从数据绑定属性设置器中调用异步方法的正确方法?

时间:2014-01-06 14:02:04

标签: mvvm windows-runtime async-await winrt-async

现在我知道属性不支持异步/等待有充分理由。但有时您需要从属性设置器中启动一些额外的后台处理 - 一个很好的例子就是MVVM场景中的数据绑定。

在我的例子中,我有一个绑定到ListView的SelectedItem的属性。当然,我立即将新值设置为支持字段,并完成属性的主要工作。但是,UI中所选项目的更改还需要触发REST服务调用,以根据现在选择的项目获取一些新数据。

所以我需要调用异步方法。显然,我无法等待它,但我也不想激活并忘记呼叫,因为在异步处理期间我可能会错过异常。

现在我的看法如下:

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");
                    }
                });
        }
    }
}

好的,除了我可以将处理从setter转移到同步方法之外,这是处理这种情况的正确方法吗?是否有一个更好,更简洁的解决方案,我没有看到?

非常有兴趣看到其他一些问题。我有点好奇我无法找到关于这个具体主题的任何其他讨论,因为在MVVM应用程序中,我很常见的是大量使用数据绑定。

2 个答案:

答案 0 :(得分:12)

我的NotifyTaskCompletion type in my AsyncEx library基本上是INotifyPropertyChanged / Task的{​​{1}}包装器。 AFAIK目前在Task<T>上结合MVVM的信息很少,所以如果你找到其他方法,请告诉我。

无论如何,如果您的任务返回结果,async方法效果最佳。即,从您当前的代码示例中看起来NotifyTaskCompletion将数据绑定属性设置为副作用而不是返回文章。如果您改为返回GetFeedArticles,则可以使用以下代码:

Task<T>

然后,您的数据绑定可以使用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) { ... } 来获取生成的集合(Articles.Result直到null完成)。您可以使用GetFeedArticlesAsync“开箱即用”来对数据进行数据绑定(例如NotifyTaskCompletion),并且它有一些布尔方便属性(Articles.ErrorMessage,{{1} })处理可见性切换。

请注意,这将正确处理无序完成的操作。由于IsSuccessfullyCompleted实际上表示异步操作本身(而不是直接结果),因此在启动新操作时会立即更新它。所以你永远不会看到过时的结果。

您没有 使用数据绑定进行错误处理。您可以通过修改IsFaulted来制作所需的语义;例如,通过将异常传递到Articles

来处理异常
GetFeedArticlesAsync

同样,没有内置自动取消的概念,但同样很容易添加到MessengerInstance

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

这是当前开发的一个领域,因此请做出改进或API建议!

答案 1 :(得分:0)

public class AsyncRunner
{
    public static void Run(Task task, Action<Task> onError = null)
    {
        if (onError == null)
        {
            task.ContinueWith((task1, o) => { }, TaskContinuationOptions.OnlyOnFaulted);
        }
        else
        {
            task.ContinueWith(onError, TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

属性中的用法

private NavigationMenuItem _selectedMenuItem;
public NavigationMenuItem SelectedMenuItem
{
    get { return _selectedMenuItem; }
    set
    {
        _selectedMenuItem = val;
         AsyncRunner.Run(NavigateToMenuAsync(_selectedMenuItem));           
    }
}
private async Task NavigateToMenuAsync(NavigationMenuItem newNavigationMenu)
{
    //call async tasks...
}