Reputation: 16453
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.
Upvotes: 15
Views: 12421
Reputation: 16453
Since I posted this question and for over a year now in my projects I've been going without any MVVM framework because I can't stand being tied to a framework that may break with the latest Xamarin.Forms. I ended up calling my async init method in my viewmodel from the xaml's appearing event tied to a behavior.
Upvotes: 3
Reputation: 1729
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.
Upvotes: 2
Reputation: 16199
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.
Upvotes: 16