question

JesseKnott-2239 avatar image
0 Votes"
JesseKnott-2239 asked JesseKnott-2239 commented

Problem with async tasks not firing when told to?

Hello, I am having a problem with reloading data on a page when the OnAppearing fires.
What appears to happen when stepping through the debugger is as follows.

  1. The page is created, and the Task is assigned. While assigning the function to the task it is run for the first time (probably should add logic to prevent a full run when only assigning, but I can do that later)

  2. The page is displayed with the proper record data as expected.

  3. The user clicks a button to add a new record.

  4. The add record page loads as expected. The add/modify data page uses the same viewmodel but creates it's own new instance (another reason to prevent the laoding of the inventory when only working on a specific record)

  5. When the user is done and clicks save, the record is saved to the database, and the the page is closed with await Navigation.PopAsync(true);

  6. When the inventory page is shown again, the OnAppearing function fires calling the RefreshPage function, but when it comes to the line to reload the inventory, the InitInventory function never fires.

I've tried stepping the code, but when it gets to the InitInventory line, it simply steps over it, and the function is never run. (I've tried adding debug code to output to the console when the function runs, and that output is never sent. I've also tried setting break points within the function and they never break) The watch results for the task say that the task ran to completion, but that's even before it's called (I am assuming it's a st ale state from the initialization).
How can I force the function to fire? should I just give up on the dynamic task assignment, and just call the targeted function directly?

Here is a basic rundown of the code.

BaseViewModel.cs

 public class BaseViewmodel
 {
     ....
         public Task InitInventory{ get; internal set; }
     ....
 }

itemviewmodel.cs

 public class ItemViewModel : BaseViewmodel
 {
   ....
   public ItemViewModel()
   {
     InitInventory = ExecuteLoadItemsCommand();
   }    

   internal async Task ExecuteLoadItemsCommand()
   {
      try
      {
          if (IsBusy == true)
              return;
          else
              IsBusy = true;

          if (ItemsList == null)
              ItemsList = new ObservableCollection<ItemModel>();

          ItemsList.Clear();

          ItemsList = await ItemsInventoryRepository.GetItems();
          foreach (var item in ItemsList)
          {
             item.Photos = new ObservableCollection<Photos>(await PhotoRepo.GetItemByParentID(item.Id, (int)RecordCatagory.Supplies, (int)RecordType.Product).ConfigureAwait(true));
          }
          //Refresh the items list for the main screen
          OnPropertyChanged(nameof(ItemsList));
      }
      catch (Exception ex)
      {
          DebugTools.LogException(ex);
      }
      finally
      {
          IsBusy = false;
      }
   }
 }

ProductInventory.xaml.cs

 {
   ...
   internal ItemViewModel viewModel;
      
   public ProductInventory()
   {
       try
       {
           InitializeComponent();

           BindingContext = (viewModel = new ItemViewModel());

           BuildForm();
       }
       catch (Exception ex)
       {
           DebugTools.LogException(ex);
       }
   }

   //When the page appears, reload the data and show it
   protected override async void OnAppearing()
   {
       try
       {
           base.OnAppearing();
           await RefreshPage().ConfigureAwait(true);
       }
       catch (Exception ex)
       {
           DebugTools.LogException(ex);
       }
   }

   private async void BuildForm()
   {
      try
      {
          await viewModel.InitInventory.ConfigureAwait(true);
          dataGrid.ItemsSource = viewModel.WaddingInventoryList;
          //Build the display for the grid
      }
      catch (Exception ex)
      {
          DebugTools.LogException(ex);
      }
   }

   private async Task RefreshPage()
   {
       try
       {
           if (viewModel != null)
           {
               await viewModel.InitInventory;

               dgWadding.IsVisible = viewModel.WaddingInventoryList.Count > 0;

               dgWadding.ItemsSource = viewModel.WaddingInventoryList;
               dgWadding.Refresh();
           }
       }
       catch (Exception ex)
       {
           DebugTools.LogException(ex);
       }
   }
 }



As always any help is greatly appreciated!

Cheers!








dotnet-xamarin
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

You can have a try to call RefreshPage like this await RefreshPage(), without ConfigureAwait(true).





0 Votes 0 ·

For some reason no matter what I try, it will not execute the query function again using the pointer method above. The only way I can make this work is to abandon the InitInventory method, and just call the ExecuteLoadItemsCommand() command directly.

0 Votes 0 ·
WenyanZhang-MSFT avatar image
1 Vote"
WenyanZhang-MSFT answered JesseKnott-2239 commented

Hello,
Welcome to our Microsoft Q&A platform!
InitInventory is a property in BaseViewmodel and it is also a function should be invoked. The following code shows a sample:

BaseViewmodel

 public class BaseViewmodel
     {
         public Func<Task> InitInventory { get; internal set; }
     }

ItemViewModel

 public class ItemViewModel : BaseViewmodel
     {
    public ItemViewModel()
         {
             InitInventory = ExecuteLoadItemsCommand;
         }
         internal async Task ExecuteLoadItemsCommand()
         {
             Console.WriteLine("--------");
         }
     }

xaml(I define a button to refresh)

 <Button Text="refresh" Clicked="Button_Clicked"></Button>

xaml.cs

  public partial class MainPage : ContentPage
     {
         public ItemViewModel viewModel { get; set; }
         public MainPage()
         {
             InitializeComponent();
             BindingContext = (viewModel = new ItemViewModel());
                
         }
         protected override async void OnAppearing()
         {
             base.OnAppearing();
             await RefreshPage().ConfigureAwait(true);
         }
         private async Task RefreshPage()
         {
             try
             {
                 if (viewModel != null)
                 {
                     await viewModel.InitInventory.Invoke();
                 }
             }
             catch (Exception ex)
             {
                 Console.WriteLine("{0}", ex);
                   
             }
         }
    
         // action to test 
         private async void Button_Clicked(object sender, EventArgs e)
         {
             await RefreshPage().ConfigureAwait(true);
         }
     }

Best Regards,
Wenyan Zhang



If the response is helpful, please click "Accept Answer" and upvote it.
Note: Please follow the steps in our [documentation][1] to enable e-mail notifications if you want to receive the related email notification for this thread.
[1]: https://docs.microsoft.com/en-us/answers/articles/67444/email-notifications.html

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Ahh I see, I should call the .Invoke() method to ensure that it is run?
That would make sense in the long run. I suppose that in my code since I was just issuing await viewModel.InitInventory instead of await viewModel.InitInventory.Invoke() I was referencing it, but since it had previously run to completion it was not running it a second time. Alternately I could have reset the run to completion flag and it should have worked then?
Sorry if my logic is confusing, I am just trying to make sure I have a more accurate understanding of how this came about and how the logic is supposed to work.

Thanks for the help!
Cheers!

0 Votes 0 ·
JesseKnott-2239 avatar image
0 Votes"
JesseKnott-2239 answered

For some reason it seems that no matter what I cannot get the InitInventory function to fire again after the first run.
If I directly call the targeted function ExecuteLoadItemsCommand() it will work as expected, but if I use the function pointer InitInventory it will not call the function. I need this approach elsewhere in the code, but don't see why it shouldn't work here just as well.
I tried setting a break point on the line of code, await viewModel.InitInventory, when I over the mouse over it the intellisense says that it has an Id of 1, it ran to completion, and the method is (null).

For now I will just resume calling the desired function directly, but can someone please help me understand how I can make this work the way I was trying to make it?

The idea was to create a generic model that I can assign functions to. This would allow me to have generic pages and functions that change their functionality based on how I initialize them.

Cheers!

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.