我最近在WinForm应用程序中遇到了这段代码,我无法确定是否有任何理由在等待的async
中运行Task.Run
代码。
public async Task SaveStuff()
{
await Task.Run(() => SaveStuffAsync().ConfigureAwait(false));
await Task.Run(() => SendToExternalApiAsync().ConfigureAwait(false));
}
private async Task SaveStuffAsync()
{
await DbContext.SaveChangesAsync().ConfigureAwait(false);
}
private async Task SendToExternalApiAsync()
{
// some async code that is awaited with ConfigureAwait(false);
}
如果没有Task.Run,这段代码会不会做完全相同的事情?
public async Task SaveStuff()
{
await SaveStuffAsync().ConfigureAwait(false);
await SendToExternalApiAsync().ConfigureAwait(false);
}
答案 0 :(得分:6)
如果没有Task.Run,这段代码会不会做完全相同的事情?
如果async方法中的代码实际上是异步的,则不会有任何不同。 +---+-----+-----+-----+------+
| ID|Fruit|Color|Eaten|Origin|
+---+-----+-----+-----+------+
| 0|Apple| Red| true|France|
+---+-----+-----+-----+------+
将在线程池上执行,因此可能需要更多资源才能执行。从调用线程的角度来看,您不会注意到差异。
但是,如果async方法中的代码是(不是)同步的,则您会发现有所不同。请考虑以下方法:
Task
以上方法具有异步签名,但实际上将同步运行,因此在执行时阻塞了调用线程。将调用包装在private async Task DoWorkNotReallyAsync()
{
for (int i = 0; i < aVeryLargeNumber; i++)
{
DoSynchronousComputation();
}
}
中会将执行安排在线程池中。因此,如果您想确保对async方法的调用不会阻塞当前线程,则将任务包装在Task.Run
中可能会很有用。
您的示例中调用的方法看上去确实是异步的,因此我看不出将这些任务包装在Task.Run
中的原因。
答案 1 :(得分:3)
方法返回Task
的事实并不意味着它立即返回。例如,在进行I / O操作之前,可能需要一些时间/ CPU消耗设置。
因此,通常会在客户端用户界面上看到用户界面之外的所有内容都在Task.Run
内部被调用。
话虽如此,但事实并非如此:
public async Task SaveStuff()
{
await Task.Run(() => SaveStuffAsync().ConfigureAwait(false));
await Task.Run(() => SendToExternalApiAsync().ConfigureAwait(false));
}
这会导致在UI线程中执行一次额外的执行,只是为了安排线程池上的工作。
这会更容易接受:
public async Task SaveStuff()
{
await Task.Run(
async () =>
{
await SaveStuffAsync();
await SendToExternalApiAsync();
});
}
无需调用ConfigureAwait(false)
,因为可以保证没有SynchronizationContext
。
此代码段与您的最后一个代码段之间的区别是在何处以及如何调用该代码,