Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to call async methods from within a data-bound property setter?

Tags:

Now I know properties do not support async/await for good reasons. But sometimes you need to kick off some additional background processing from a property setter - a good example is data binding in a MVVM scenario.

In my case, I have a property that is bound to the SelectedItem of a ListView. Of course I immediately set the new value to the backing field and the main work of the property is done. But the change of the selected item in the UI needs also to trigger a REST service call to get some new data based on the now selected item.

So I need to call an async method. I can't await it, obviously, but I also do not want to fire and forget the call as I could miss exceptions during the async processing.

Now my take is the following:

private Feed selectedFeed; public Feed SelectedFeed {     get     {         return this.selectedFeed;     }     set     {         if (this.selectedFeed != value)         {             this.selectedFeed = value;             RaisePropertyChanged();              Task task = GetFeedArticles(value.Id);              task.ContinueWith(t =>                 {                     if (t.Status != TaskStatus.RanToCompletion)                     {                         MessengerInstance.Send<string>("Error description", "DisplayErrorNotification");                     }                 });         }     } } 

Ok so besides the fact I could move out the handling from the setter to a synchronous method, is this the correct way to handle such a scenario? Is there a better, less cluttered solution I do not see?

Would be very interested to see some other takes on this problem. I'm a bit curious that I was not able to find any other discussions on this concrete topic as it seems very common to me in MVVM apps that make heavy use of databinding.

like image 705
Bernhard Koenig Avatar asked Jan 06 '14 14:01

Bernhard Koenig


People also ask

What happens when you call an async method?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Can a property be async C#?

There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron. Properties should return current values; they should not be kicking off background operations.

How do async methods work?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.

Can I use async without await C#?

C# Language Async-Await Returning a Task without awaitMethods that perform asynchronous operations don't need to use await if: There is only one asynchronous call inside the method. The asynchronous call is at the end of the method. Catching/handling exception that may happen within the Task is not necessary.


1 Answers

I have a NotifyTaskCompletion type in my AsyncEx library that is essentially an INotifyPropertyChanged wrapper for Task/Task<T>. AFAIK there is very little information currently available on async combined with MVVM, so let me know if you find any other approaches.

Anyway, the NotifyTaskCompletion approach works best if your tasks return their results. I.e., from your current code sample it looks like GetFeedArticles is setting data-bound properties as a side effect instead of returning the articles. If you make this return Task<T> instead, you can end up with code like this:

private Feed selectedFeed; public Feed SelectedFeed {   get   {     return this.selectedFeed;   }   set   {     if (this.selectedFeed == value)       return;     this.selectedFeed = value;     RaisePropertyChanged();     Articles = NotifyTaskCompletion.Create(GetFeedArticlesAsync(value.Id));   } }  private INotifyTaskCompletion<List<Article>> articles; public INotifyTaskCompletion<List<Article>> Articles {   get { return this.articles; }   set   {     if (this.articles == value)       return;     this.articles = value;     RaisePropertyChanged();   } }  private async Task<List<Article>> GetFeedArticlesAsync(int id) {   ... } 

Then your databinding can use Articles.Result to get to the resulting collection (which is null until GetFeedArticlesAsync completes). You can use NotifyTaskCompletion "out of the box" to data-bind to errors as well (e.g., Articles.ErrorMessage) and it has a few boolean convenience properties (IsSuccessfullyCompleted, IsFaulted) to handle visibility toggles.

Note that this will correctly handle operations completing out of order. Since Articles actually represents the asynchronous operation itself (instead of the results directly), it is updated immediately when a new operation is started. So you'll never see out-of-date results.

You don't have to use data binding for your error handling. You can make whatever semantics you want by modifying the GetFeedArticlesAsync; for example, to handle exceptions by passing them to your MessengerInstance:

private async Task<List<Article>> GetFeedArticlesAsync(int id) {   try   {     ...   }   catch (Exception ex)   {     MessengerInstance.Send<string>("Error description", "DisplayErrorNotification");     return null;   } } 

Similarly, there's no notion of automatic cancellation built-in, but again it's easy to add to GetFeedArticlesAsync:

private CancellationTokenSource getFeedArticlesCts; private async Task<List<Article>> GetFeedArticlesAsync(int id) {   if (getFeedArticlesCts != null)     getFeedArticlesCts.Cancel();   using (getFeedArticlesCts = new CancellationTokenSource())   {     ...   } } 

This is an area of current development, so please do make improvements or API suggestions!

like image 63
Stephen Cleary Avatar answered Sep 29 '22 08:09

Stephen Cleary