Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Asynchronous method from ViewModel Constructor Xamarin.Forms

I can't find a straight example from a-z on how to implement calling an async method from a constructor in a safe way. Following is what I've come up with but I don't understand the concepts that well so I have no idea if it's really correct or not. Can someone bless this format?

Create IAsyncInitialization interface:

/// <summary>
/// The result of the asynchronous initialization of this instance.
/// see http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
/// </summary>
Task Initialization { get; }

Slap the interface on this ViewModel then...:

public GotoViewModel() // constructor
{
    Initialization = InitializeAsync();
}

public Task Initialization { get; private set; }
private async Task InitializeAsync()
{
    //call some async service and get data
}

From the code-behind xaml.cs that uses this ViewModel:

public partial class GotoPage : ContentPage, IAsyncInitialization
{
    IGotoViewModel VM;
    public GotoPage()
    {
         InitializeComponent();
         VM = App.Container.Resolve<IGotoViewModel>();
         Initialization = InitializeAsync();
     }

     public Task Initialization { get; private set; }

     private async Task InitializeAsync()
     {
          await VM.Initialization;
          this.BindingContext = VM;
     }
}

This code works great but I know that doesn't mean much.

like image 433
Post Impatica Avatar asked May 18 '17 20:05

Post Impatica


People also ask

Can a constructor call async function?

A simple answer for that: No, we can't! Currently, class constructors do not return types, and an asynchronous method should return a Task type. Don't worry about that, because there is a solution for this.

Can I use async await in constructor?

You can only use async/await where you can use promises because they are essentially syntax sugar for promises. You can't use promises in a constructor because a constructor must return the object to be constructed, not a promise.

Can we use await in constructor C#?

It's possible to call this in the constructor, but you can't await an expression that referenced it.


2 Answers

Possibly upcoming a future version on c# is the ability to have async constructors, but until we reach that awesome future here are your options.

You can use the

.Wait()

However in doing this you need to be very aware of what threads you are running on. If the VM is being called in a UI Thread, then you will lock the UI until the async tasks finishes. If the async task also uses something in the UI thread, then here you get a deadlock.

A way to alleviate that on some conditions is via doing

Task.Run(async () => { await Initialize(); }).Wait();

But again, its not foolproof and possible deadlocks await.

The other option is to do some very advanced stuff, that even I don't completely understand what this code is doing, but it does work.

If you look at the Exrin ThreadHelper.cs it contains the method

public static void RunOnUIThread(Func<Task> action)

This allows you to run a task, in the same UIThread, while not blocking it. Because sometimes you need to run an async task, in the UI Thread, and wait for it. It's complicated stuff but possible.

Now that you know why its really tricky to do async from a constructor. Here is the easy way.

Since your page is bound to the ViewModel, the best way is to relay the OnAppearing method to it. Hence in your page you do

public async void OnAppearing()
{
    await MyViewModelInstance.OnAppearing();
}

Then in your ViewModel you can do

public async Task OnAppearing()
{
    await InitializeAsync();
}

async void's are perfectly acceptable, if you are sure that the one calling the method doesn't need a Task to wait for the event to finish.

This way the initialize runs when the View appears and not on its construction. The approach applies in the App.xaml.cs where you would do this in the OnStart, instead of the constructor.

like image 67
Adam Avatar answered Nov 01 '22 22:11

Adam


Instead of calling it in your constructor or using the OnAppearing method that Adam Pedley mentioned, you can also use a simple MVVM-Framework like FreshMvvm. In FreshMVVM you can override the init() method to initialize your objects.

In my current project, i initialize my ViewModels by using Autofac as a IoC-Container and do the loading stuff within the ViewModel after Autofac created its instance.

like image 2
Tobias von Falkenhayn Avatar answered Nov 02 '22 00:11

Tobias von Falkenhayn