wax*_*cal 9 c# wpf asynchronous mvvm async-await
我一直在尝试为WPF应用程序编写MVVM屏幕,使用async和await关键字为1编写异步方法.最初加载数据,2.刷新数据,3.保存更改然后刷新.虽然我有这个工作,但代码非常混乱,我不禁想到必须有更好的实现.任何人都可以建议更简单的实施?
这是我的ViewModel的简化版本:
public class ScenariosViewModel : BindableBase
{
public ScenariosViewModel()
{
SaveCommand = new DelegateCommand(async () => await SaveAsync());
RefreshCommand = new DelegateCommand(async () => await LoadDataAsync());
}
public async Task LoadDataAsync()
{
IsLoading = true; //synchronously set the busy indicator flag
await Task.Run(() => Scenarios = _service.AllScenarios())
.ContinueWith(t =>
{
IsLoading = false;
if (t.Exception != null)
{
throw t.Exception; //Allow exception to be caught on Application_UnhandledException
}
});
}
public ICommand SaveCommand { get; set; }
private async Task SaveAsync()
{
IsLoading = true; //synchronously set the busy indicator flag
await Task.Run(() =>
{
_service.Save(_selectedScenario);
LoadDataAsync(); // here we get compiler warnings because not called with await
}).ContinueWith(t =>
{
if (t.Exception != null)
{
throw t.Exception;
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
IsLoading暴露在绑定到忙指示符的视图中.
首次查看屏幕或按下刷新按钮时,导航框架将调用LoadDataAsync.此方法应同步设置IsLoading,然后将控制权返回给UI线程,直到服务返回数据.最后抛出任何异常,以便它们可以被全局异常处理程序捕获(不用讨论!).
SaveAync由按钮调用,将更新的值从表单传递到服务.它应该同步设置IsLoading,异步调用服务上的Save方法,然后触发刷新.
Ste*_*ary 19
跳出来的代码中有一些问题:
ContinueWith.ContinueWith是一个危险的API(它有一个令人惊讶的默认值TaskScheduler,因此它应该只在你指定a时使用TaskScheduler).与等效await代码相比,它也只是简单的尴尬.Scenarios从线程池线程设置.我始终遵循我的代码中的指南,即数据绑定的VM属性被视为UI的一部分,并且只能从UI线程访问.这条规则有例外(特别是在WPF上),但它们在每个MVVM平台上都不一样(并且一开始就是一个值得怀疑的设计,IMO),因此我只将虚拟机视为UI层的一部分.Application.UnhandledException,但我不认为此代码会这样做.假设TaskScheduler.Current是null在LoadDataAsync/ 的开头SaveAsync,那么重新提升的异常代码实际上会在线程池线程而不是UI线程上引发异常,从而将其发送到AppDomain.UnhandledException而不是Application.UnhandledException.LoadDataAsync没有电话await.使用这个简化的代码,它可能会工作,但它确实引入了忽略未处理的异常的可能性.特别是,如果LoadDataAsync抛出任何同步部分,那么该异常将被默默地忽略.我建议只使用更自然的异常传播方法,而不是乱搞手动异常重新抛出await:
await 将检查此异常,并以适当的方式重新提升它(保留原始堆栈跟踪).async void方法没有放置异常的任务,因此它们会直接在它们上面重新引发它SynchronizationContext.在这种情况下,由于您的async void方法在UI线程上运行,因此将发送异常Application.UnhandledException.(async void我所指的方法是async传递给的代表DelegateCommand).
代码现在变成:
public class ScenariosViewModel : BindableBase
{
public ScenariosViewModel()
{
SaveCommand = new DelegateCommand(async () => await SaveAsync());
RefreshCommand = new DelegateCommand(async () => await LoadDataAsync());
}
public async Task LoadDataAsync()
{
IsLoading = true;
try
{
Scenarios = await Task.Run(() => _service.AllScenarios());
}
finally
{
IsLoading = false;
}
}
private async Task SaveAsync()
{
IsLoading = true;
await Task.Run(() => _service.Save(_selectedScenario));
await LoadDataAsync();
}
}
Run Code Online (Sandbox Code Playgroud)
现在所有问题都已解决:
ContinueWith已被替换为更合适await.Scenarios 是从UI线程设置的.Application.UnhandledException而不是AppDomain.UnhandledException.await执行的任务,因此将以某种方式观察所有异常.而且代码也更清晰.IMO.:)