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!