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

Mvvm 从数据绑定属性设置器中调用异步方法的正确方法?,mvvm,windows-runtime,async-await,winrt-async,Mvvm,Windows Runtime,Async Await,Winrt Async,现在我知道属性不支持async/await有很好的理由。但有时您需要从属性设置程序启动一些额外的后台处理—MVVM场景中的数据绑定就是一个很好的例子 在我的例子中,我有一个绑定到ListView的SelectedItem的属性。当然,我立即将新值设置到backing字段,属性的主要工作就完成了。但是UI中所选项目的更改还需要触发REST服务调用,以基于当前所选项目获取一些新数据 所以我需要调用一个异步方法。显然,我不能等待它,但我也不想触发并忘记调用,因为在异步处理过程中可能会错过异常 现在我的

现在我知道属性不支持async/await有很好的理由。但有时您需要从属性设置程序启动一些额外的后台处理—MVVM场景中的数据绑定就是一个很好的例子

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

所以我需要调用一个异步方法。显然,我不能等待它,但我也不想触发并忘记调用,因为在异步处理过程中可能会错过异常

现在我的看法如下:

private Feed selected Feed;
公共提要SelectedFeed
{
得到
{
返回此。selectedFeed;
}
设置
{
if(this.selectedFeed!=值)
{
this.selectedFeed=值;
RaisePropertyChanged();
任务=GetFeedArticles(value.Id);
task.ContinueWith(t=>
{
if(t.Status!=TaskStatus.RanToCompletion)
{
Send(“错误描述”、“显示错误通知”);
}
});
}
}
}
除了我可以将处理从setter转移到同步方法之外,这是处理这种情况的正确方法吗?有没有一个更好的、更少混乱的解决方案我看不到

我很想看看这个问题的其他方面。我有点好奇,因为在大量使用数据绑定的MVVM应用程序中,我似乎很常见,所以我找不到关于这个具体主题的任何其他讨论。

我有一个基本上是
INotifyPropertyChanged
包装的
任务
/
任务
。AFAIK目前关于
async
与MVVM结合的信息非常少,所以如果您找到任何其他方法,请告诉我

无论如何,如果任务返回结果,
NotifyTaskCompletion
方法效果最好。也就是说,从您当前的代码示例来看,
GetFeedArticles
将数据绑定属性设置为副作用,而不是返回文章。如果您改为执行此返回任务,则可能会得到如下代码:

private Feed selected Feed;
公共提要SelectedFeed
{
得到
{
返回此。selectedFeed;
}
设置
{
if(this.selectedFeed==值)
返回;
this.selectedFeed=值;
RaisePropertyChanged();
Articles=NotifyTaskCompletion.Create(GetFeedArticlesAsync(value.Id));
}
}
私人INotifyTaskCompletion文章;
公共INotifyTaskCompletion文章
{
获取{返回this.articles;}
设置
{
if(this.articles==值)
返回;
这就是价值;
RaisePropertyChanged();
}
}
专用异步任务GetFeedArticlesAsync(int id)
{
...
}
然后,您的数据绑定可以使用
Articles.Result
访问结果集合(在
GetFeedArticlesAsync
完成之前,该集合为
null
)。您也可以使用
NotifyTaskCompletion
“开箱即用”将数据绑定到错误(例如,
Articles.ErrorMessage
),并且它有一些布尔便利属性(
issucessfullycompleted
IsFaulted
)来处理可见性切换

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

您不必使用数据绑定来处理错误。通过修改
GetFeedArticlesAsync
,您可以创建任何语义;例如,通过将异常传递给您的
Messenger实例来处理异常:

专用异步任务GetFeedArticlesAsync(int-id)
{
尝试
{
...
}
捕获(例外情况除外)
{
Send(“错误描述”、“显示错误通知”);
返回null;
}
}
类似地,没有内置自动取消的概念,但同样很容易添加到
GetFeedArticlesAsync

private CancellationTokenSource getFeedArticlesCts;
专用异步任务GetFeedArticlesAsync(int id)
{
if(getFeedArticlesCts!=null)
getFeedArticlesCts.Cancel();
使用(getFeedArticlesCts=new CancellationTokenSource())
{
...
}
}
这是当前开发的一个领域,因此请务必做出改进或提出API建议

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


这里要注意的有趣的事情是在REST请求进行过程中处理您的属性更改。特别是因为您不能保证它们将按照调用顺序完成。是的,确实如此:)但无论我使用属性设置程序启动REST调用或事件或其他任何操作,都会出现此问题。我的做法是在提交新项目之前,每当所选项目发生更改时,取消所有仍在运行的请求。您可以将命令附加到“选择已更改”事件,而不是检查所选项目是否已被UI更改。谢谢Stephen,这是一种有趣的方法。这似乎也是从ViewModel的构造函数中启动异步数据加载的好方法。目前,我使用EventToCommand调用VM上的一个方法,用数据填充VM(当然,我也不太喜欢)。我认为代码比我的方法更清晰。查看AsyncEx的源代码,我希望看到编译器警告CS4014被忽略了。在玩了一会儿之后,实际发生的似乎是
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...
}