async
-await
概念很容易理解,但是,我很难掌握ContinueWith
。
在下面的示例中,我想一个接一个地运行 2 个异步任务(LoadAsync 和 ComputeAsync),同时,我想做其他事情。 方法#1是我熟悉的方法。
方法 #2、#3 和 #4 对于实现与 #1 相同的结果是否正确? 有人可以解释/评论幕后有什么区别吗? 谢谢!
方法 #1 - 在async
函数中使用await
public async int LoadAndComputeAsync
{
var data = await LoadAsync();
return await ComputeAsync(data);
}
Task<int> task1 = LoadAndComputeAsync();
DoSomethingElse();
int result = await task1;
方法#2 - 在Task.Run
中同步执行
Task<int> task2 = Task.Run(() => {
var data = LoadAsync().Result;
return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task2;
方法#3 - 将ContinueWith
与async
一起使用,Unwrap
Task<int> task3 = LoadAsync().ContinueWith(async(t) => {
var data = t.Result;
return await ComputeAsync(data);
}).Unwrap();
DoSomethingElse();
int result = await task3;
方法#4 - 在ContinueWith
中同步执行
Task<int> task4 = LoadAsync().ContinueWith(t => {
var data = t.Result;
return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task4;
,我很难掌握
ContinueWith
.
使用ContinueWith
最简单的方法是拼写为"await"。
不,说真的。ContinueWith
是一个具有令人惊讶的默认行为的低级 API。这将使您的代码更复杂,更难维护,同时不会带来任何好处。所以我的问题是"为什么?
也就是说,下面将为您提供一些答案,但这些答案仅用于教学目的,而不是生产代码。
首先,Task<T>.Result
具有不同的异常处理行为;它将把所有异常包装在一个AggregateException
中,而不是直接引发它们。这是因为Task<T>
最初是为并行编程设计的,而不是异步编程;但是当添加async
/await
时,Microsoft决定只重用现有的Task
/Task<T>
类型,而不是创建更异步的本机类型。对于异步代码,请将.Result
替换为.GetAwaiter().GetResult()
。
接下来,async
不会将工作排队到线程池。Task.Run
做到了。所以这是方法#2的另一个区别。
您的方法#3非常接近。如果将.Result
替换为.GetAwaiter().GetResult()
,您仍然会遇到ContinueWith
使用的TaskScheduler
问题,默认为TaskScheduler.Current
,这可能不是您想要的(在异步代码中,通常不是)。切勿在未指定TaskScheduler
的情况下使用ContinueWith
。此外,将ContinueWith
与async
一起使用很奇怪 - 如果您无论如何都在使用方法async
,为什么不只执行方法 #1?
方法#4确实阻塞了ContinueWith
中的线程。
如果你想真实地复制方法#1,你需要:
- 防止异常包装在
AggregateException
中。 - 始终将显式
TaskScheduler
传递给ContinueWith
。 - 使用其他适当的
ContinueWith
标志,使其行为更加异步友好。 - 捕获适当的上下文并在该上下文中执行延续。
下面是一个示例:
// Original
public async Task<int> LoadAndComputeAsync()
{
var data = await LoadAsync();
return await ComputeAsync(data);
}
// Using ContinueWith
public Task<int> LoadAndComputeTheHardWayAsync()
{
var scheduler = SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current;
return LoadAsync().ContinueWith(t =>
{
var data = t.GetAwaiter().GetResult();
return ComputeAsync(data);
},
CancellationToken.None,
TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
scheduler).Unwrap();
}
- 方法#2与它通过Task.Run创建的其他方法之间的最大区别 async/await 永远不会创建新线程 - 它会在调用它的同一线程上执行延续。 方法#
- 1和方法#3几乎相同的方法