ConfigureAwait和混合异步调用

时间:2015-07-28 09:25:26

标签: c# multithreading asynchronous async-await

我一直在阅读有关异步/等待编程模型的一些文章,还有一些不太清楚的事情,我想分享我对这些的困惑。

假设我们有以下配置:

主要的异步方法是public async Task<bool> DoSomethingBigAsync(){...},其内部有3个其他方法,如下所示:

A)var a = await _someInstance.DoSomethingLittle_A_Async().ConfigureAwait(false);
B)var b = await _someInstance.DoSomethingLittle_B_Async();
C)var c = _someInstance.DoSomethingLittle_C();

例如,从UI线程调用main方法。我们知道将捕获上下文,并且当main方法完成时,将恢复上下文。

如果我没错,当第一个( A )异步方法被调用时,由于ConfigureAwait(false)没有捕获UI上下文而是推送了延续从池中的线程。但这并不能保证会发生,因为呼叫可能会立即完成。出于这个原因,我怀疑,应该在所有内部异步方法上调用ConfigureAwait(false)(在这种情况下, A B )。这是正确的,还是运行时在看到第一次调用ConfigureAwait(false)之后知道该怎么做?

我的另一个问题是关于两种不良做法(或被认为是这样)及其副作用。

根据Stephen Toub的文章,似乎有一种不好的做法:

  

为库中的同步方法公开异步包装器   很糟糕

因此,制作DoSomethingLittle_C()方法的异步版本不是一个好主意。

其他不良做法似乎是:

  

不要混用阻塞和异步代码。

现在,看一下上面的代码,如果第一个ConfigureAwait(false)可以保证继续被推送到线程池,那么在使用Task.StartNew(() => { c = _someInstance.DoSomethingLittle_C(); })时我没有看到任何附加价值。 另一方面,如果ConfigureAwait(false)永远不会保证将延续推送到线程池,那么我们可能会遇到问题,我们必须确保使用Task.StartNew(() => { c = _someInstance.DoSomethingLittle_C(); })

WRONG(?):
A)var a = await _someInstance.DoSomethingLittle_A_Async().ConfigureAwait(false);
B)var b = await _someInstance.DoSomethingLittle_B_Async();
C)var c = _someInstance.DoSomethingLittle_C();

CORRECT(?):
A)var a = await _someInstance.DoSomethingLittle_A_Async().ConfigureAwait(false);
B)var b = await _someInstance.DoSomethingLittle_B_Async().ConfigureAwait(false);
C)var c = Task.StartNew(() => {return _someInstance.DoSomethingLittle_C();});

1 个答案:

答案 0 :(得分:8)

  

&#34;出于这个原因,我怀疑,应该在所有内部异步方法(本例中为A和B)上调用ConfigureAwait(false)。这是正确的,还是运行时在看到第一次调用ConfigureAwait(false)后知道该怎么做?&#34;

是。这应该。因为,正如您所说,之前的通话可能会同步完成,也可能是因为之前的通话可能会在未来发生变化,您也不想依赖它。

关于Task.Run(首选Task.Factory.StartNew),问题是是否在API的实施中使用它,答案几乎总是没有。

你的情况不同。如果你在UI线程上并且你有大量的工作(斯蒂芬克莱里的建议超过50毫秒)可以在后台完成,那么将工作卸载到ThreadPool并没有错线程以保持UI响应。就像ConfigureAwait一样,我不会依赖之前的调用将您转移到ThreadPool,因为它也可以同步完成。

所以,正确的:

var a = await _someInstance.DoSomethingLittle_A_Async().ConfigureAwait(false);
var b = await _someInstance.DoSomethingLittle_B_Async().ConfigureAwait(false);
var c = await Task.Run(() => _someInstance.DoSomethingLittle_C()).ConfigureAwait(false);