Ashley Jackson
Ashley Jackson

Reputation: 173

async void when overriding

I'm aware of the evils of async void methods but am not entirely sure of the best way of over coming the problem when overriding methods.

Say, I have the following code:

protected override async void PageRefreshed()
{
    BoundDataField = await LoadDataFromWeb();
}

I know this is a really bad thing to do, but what is the best solution for this?

  1. LoadDataFromWeb().ContinueWith(r => BoundDateField = r.Result);
  2. Task.Run(async ()=> await LoadDataFromWeb())).Wait;
  3. LoadDataFromWeb().Wait
  4. BoundDataField = LoadDataFromWeb.Result

I'm pretty sure that 3 & 4 are real no nos as they will be blocking the UI thread. Is there another solution I have missed?

Upvotes: 3

Views: 1210

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457057

I'm pretty sure that 3 & 4 are real no nos as they will be blocking the UI thread.

Not just blocking, but quite possibly deadlocking, too.

Is there another solution I have missed?

What you're trying to do is asynchronously retrieve the value of a data-bound property. I cover this in detail in my MSDN article on asynchronous data binding.

First, the central thing to recognize is that this is impossible, as written. There are two conflicting requirements:

  • The computer must display something immediately.
  • You need to get the data to display, and this will take time.

So, you'll need to compromise:

  • The computer gets some placeholder data or a spinner or something to display immediately.
  • You update the display with the real data when it arrives.

Put this way, the code is more straightforward:

protected override async void PageRefreshed()
{
  BoundDataField = "placeholder"; // synchronous immediate placeholder data
  BoundDataField = await LoadDataFromWeb(); // asynchronous update
}

or:

protected override async void PageRefreshed()
{
  // Synchronously show spinner
  IsBusy = true;

  // Asynchronously load data and then hide spinner
  BoundDataField = await LoadDataFromWeb();
  IsBusy = false;
}

Note that this simple solution is not handling errors well and also doesn't handle multiple "refreshing"s possibly updating that field out of order. A more advanced approach is to use something like my NotifyTask<T> type from the Nito.Mvvm.Async NuGet package:

protected override void PageRefreshed()
{
  BoundDataField = NotifyTask<TData>.Create(LoadDataFromWeb());
}

This approach requires updates to your data binding code as well; BoundDataField.Result is now the actual data value, and BoundDataField.IsNotCompleted, BoundDataField.IsFaulted, and other properties can be used to have your data binding respond to in-progress or faulted states.

Upvotes: 7

Related Questions