[英]MVVM async await pattern
I've been trying to write an MVVM screen for a WPF application, using the async & await keywords to write asynchronous methods for 1. Initially loading data, 2. Refreshing the data, 3. Saving changes and then refreshing. 我一直在尝试为WPF应用程序编写MVVM屏幕,使用async和await关键字为1编写异步方法。最初加载数据,2。刷新数据,3。保存更改然后刷新。 Although I have this working, the code is very messy and I can't help thinking that there must be a better implementation.
虽然我有这个工作,但代码非常混乱,我不禁想到必须有更好的实现。 Can anyone advise on a simpler implementation?
任何人都可以建议更简单的实施?
This is a cut-down version of my ViewModel: 这是我的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;
}
});
}
}
IsLoading is exposed to the view where it is bound to a busy indicator. IsLoading暴露在绑定到忙指示符的视图中。
LoadDataAsync is called by the navigation framework when the screen is first viewed, or when a refresh button is pressed. 首次查看屏幕或按下刷新按钮时,导航框架将调用LoadDataAsync。 This method should synchronously set IsLoading, then return control to the UI thread until the service has returned the data.
此方法应同步设置IsLoading,然后将控制权返回给UI线程,直到服务返回数据。 Finally throwing any exceptions so they can be caught by the global exception handler (not up for discussion!).
最后抛出任何异常,以便它们可以被全局异常处理程序捕获(不用讨论!)。
SaveAync is called by a button, passing updated values from a form to the service. SaveAync由按钮调用,将更新的值从表单传递到服务。 It should synchronously set IsLoading, asynchronously call the Save method on the service and then trigger a refresh.
它应该同步设置IsLoading,异步调用服务上的Save方法,然后触发刷新。
There are a few problems in the code that jump out to me: 跳出来的代码中有一些问题:
ContinueWith
. ContinueWith
用法。 ContinueWith
is a dangerous API (it has a surprising default value for its TaskScheduler
, so it should really only be used if you specify a TaskScheduler
). ContinueWith
是一个危险的API(它的TaskScheduler
有一个令人惊讶的默认值,因此它应该只在你指定一个TaskScheduler
)。 It's also just plain awkward compared to the equivalent await
code. await
代码相比,它也只是简单的尴尬。 Scenarios
from a thread pool thread. Scenarios
。 I always follow the guideline in my code that data-bound VM properties are treated as part of the UI and must only be accessed from the UI thread. Application.UnhandledException
, but I don't think this code will do that. Application.UnhandledException
,但我认为此代码不会这样做。 Assuming TaskScheduler.Current
is null
at the start of LoadDataAsync
/ SaveAsync
, then the re-raising exception code will actually raise the exception on a thread pool thread, not the UI thread, thus sending it to AppDomain.UnhandledException
rather than Application.UnhandledException
. LoadDataAsync
/ SaveAsync
开始时TaskScheduler.Current
为null
,则重新引发的异常代码实际上会在线程池线程而不是UI线程上引发异常,从而将其发送到AppDomain.UnhandledException
而不是Application.UnhandledException
。 LoadDataAsync
without an await
. await
情况下调用LoadDataAsync
。 With this simplified code, it'll probably work, but it does introduce the possibility of ignoring unhandled exceptions. LoadDataAsync
throws, then that exception would be silently ignored. LoadDataAsync
任何同步部分抛出,则会以静默方式忽略该异常。 Instead of messing around with the manual-exception-rethrows, I recommend just using the more natural approach of exception propagation through await
: 相反,与手动异常重新抛出乱搞的,我建议只使用异常传播的更自然的方法,通过
await
:
await
will examine this exception, and re-raise it in a proper way (preserving the original stack trace). await
将检查此异常,并以适当的方式重新提升它(保留原始堆栈跟踪)。 async void
methods do not have a task on which to place an exception, so they will re-raise it directly on their SynchronizationContext
. async void
方法没有放置异常的任务,因此它们将直接在SynchronizationContext
上重新引发它。 In this case, since your async void
methods run on the UI thread, the exception will be sent to Application.UnhandledException
. async void
方法在UI线程上运行,因此异常将发送到Application.UnhandledException
。 (the async void
methods I'm referring to are the async
delegates passed to DelegateCommand
). (我所指的
async void
方法是传递给DelegateCommand
的async
委托)。
The code now becomes: 代码现在变成:
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();
}
}
Now all the problems have been resolved: 现在所有问题都已解决:
ContinueWith
has been replaced with the more appropriate await
. ContinueWith
已经被更合适的await
所取代。 Scenarios
is set from the UI thread. Scenarios
是从UI线程设置的。 Application.UnhandledException
rather than AppDomain.UnhandledException
. Application.UnhandledException
而不是AppDomain.UnhandledException
。 await
-ed tasks, so all exceptions will be observed some way or another. await
-ed任务,所以所有的异常会观察到一些这样或那样的。 And the code is cleaner, too. 而且代码也更清晰。 IMO.
IMO。 :)
:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.