这是我最近用来说明使用异步解决方案的区别和各种问题的代码片段序列。
假设您的基于GUI的应用程序中有一些事件处理程序,该处理程序会花费很多时间,因此您希望使其异步。这是您开始的同步逻辑:
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItem返回一个Task,该Task最终会产生一些您想检查的结果。如果当前结果是您要查找的结果,则更新UI上某个计数器的值,然后从该方法返回。否则,您将继续处理来自LoadNextItem的更多项目。
异步版本的第一个想法:只需使用延续!让我们暂时忽略循环部分。我的意思是,可能会出什么问题?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
太好了,现在我们有了一个不会阻塞的方法!它崩溃了。对UI控件的任何更新都应在UI线程上进行,因此您需要考虑到这一点。值得庆幸的是,有一个选项可以指定应该如何安排连续性,并且为此有一个默认选项:
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
太好了,现在我们有了不会崩溃的方法!它会以静默方式失败。连续本身是独立的任务,其状态与先前任务的状态无关。因此,即使LoadNextItem错误,调用方也只会看到已成功完成的任务。好吧,那就传一个异常,如果有的话:
return LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
throw t.Exception.InnerException;
}
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
太好了,现在这确实有效。对于单个项目。现在,如何循环。事实证明,与原始同步版本的逻辑等效的解决方案如下所示:
Task AsyncLoop() {
return AsyncLoopTask().ContinueWith(t =>
Counter.Value = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
var tcs = new TaskCompletionSource<int>();
DoIteration(tcs);
return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
tcs.TrySetException(t.Exception.InnerException);
} else if (t.Result.Contains("target")) {
tcs.TrySetResult(t.Result.Length);
} else {
DoIteration(tcs);
}});
}
或者,您可以使用async代替上述所有操作来做同样的事情:
async Task AsyncLoop() {
while (true) {
string result = await LoadNextItem();
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
}
现在好多了,不是吗?
Wait
在第二个示例中删除了该呼叫,则这两个摘要将(大部分)等效。