现在我知道属性不支持异步/等待有充分理由。但有时您需要从属性设置器中启动一些额外的后台处理 - 一个很好的例子就是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应用程序中,我很常见的是大量使用数据绑定。
答案 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...
}