C# ConfigureWait并混合异步和同步调用
我已经阅读了很多关于异步/等待编程模型的文章,但仍有一些事情不太清楚,我想与大家分享我对这些问题的困惑 假设我们有以下配置: 主要的异步方法是C# ConfigureWait并混合异步和同步调用,c#,multithreading,asynchronous,async-await,C#,Multithreading,Asynchronous,Async Await,我已经阅读了很多关于异步/等待编程模型的文章,但仍有一些事情不太清楚,我想与大家分享我对这些问题的困惑 假设我们有以下配置: 主要的异步方法是public async Task DoSomethingBigAsync(){…},它在内部有3个其他方法,如下所示: A) var A=await\u someInstance.DoSomethingLittle\u A\u Async().ConfigureAwait(false) B) var B=wait _someInstance.DoSome
public async Task DoSomethingBigAsync(){…}
,它在内部有3个其他方法,如下所示:
A) var A=await\u someInstance.DoSomethingLittle\u A\u Async().ConfigureAwait(false)代码>
B) var B=wait _someInstance.DoSomethingLittle_B_Async()代码>
C) var C=\u someInstance.DoSomethingLittle\u C()代码>
主方法是从UI线程调用的。我们知道上下文将被捕获,当主方法完成时,上下文将被恢复
如果我没有记错的话,当调用第一个(A)异步方法时,由于ConfigureAwait(false)
的原因,UI上下文不会被捕获,而是将延续从池中推送到线程。但这并不一定会发生,因为通话可能会立即完成。出于这个原因,我怀疑应该对所有内部异步方法调用ConfigureAwait(false)
(A和B)。这是否正确,或者运行时在看到对ConfigureAwait(false)
的第一个调用后知道该做什么
我的另一个担忧是两个坏习惯(或被认为是坏习惯)及其副作用
根据Stephen Toub的文章,一种不好的做法似乎是:
公开库中同步方法的异步包装器
不好
因此,创建DoSomethingLittle\u C()
方法的异步版本不是一个好主意
另一个坏习惯似乎是:
不要混合使用块代码和异步代码
现在,看看上面的代码,如果第一个ConfigureAwait(false)
可以保证将continuation推送到线程池,那么使用Task.StartNew(()=>{c=\u someInstance.DoSomethingLittle\u c()})不会有任何附加值。
另一方面,如果ConfigureAwait(false)
从不保证将continuation推送到线程池,那么我们可能会遇到问题,我们必须确保使用Task.StartNew(()=>{c=\u someInstance.DoSomethingLittle\u c();})
错误(?):
A) var A=await\u someInstance.DoSomethingLittle\u A\u Async().ConfigureAwait(false)代码>
B) var B=wait _someInstance.DoSomethingLittle_B_Async()代码>
C) var C=\u someInstance.DoSomethingLittle\u C()代码>
正确(?):
A) var A=await\u someInstance.DoSomethingLittle\u A\u Async().ConfigureAwait(false)代码>
B) var B=await\u someInstance.DoSomethingLittle\u B\u Async().ConfigureAwait(false)代码>
C) var C=Task.StartNew(()=>{return\u someInstance.DoSomethingLittle\u C()})代码>
“因此,我怀疑应该对所有内部异步方法(本例中为A和B)调用ConfigureAwait(false)。这是否正确,或者运行时在看到对ConfigureAwait(false)的第一次调用后知道该怎么做?”
对。应该这样。因为,正如你所说的,上一次通话可能会同步完成,也因为上一次通话可能会在将来更改,你不想依赖它
关于Task.Run
(这比Task.Factory.StartNew更可取),问题是是否在API的实现中使用它,答案几乎总是否定的
你的情况不同。如果您在UI线程上,并且有大量的工作(Stephen Cleary建议超过50毫秒)可以在后台完成,那么将这些工作转移到线程池
线程以保持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);
是的,当然,如果他们能有意义地升级到DoSomethingLittle\u CAsync()
,那就更好了。我还要补充一点,您应该只使用Task.Run,如果需要的话。一般的经验法则是,只有当DoSomethingLittle\u C
需要超过50ms时,才应该在这里使用它。(另外,您缺少最后一个等待
)。如果您在Task.Factory.StartNew或Task.Run中执行程序,那么程序终止情况如何?你能保证任务在终止前完成吗?我正在寻找与旧的Thread.IsBackground功能类似的功能。更准确地说,是否有一种简单的方法可以在调用链中自动执行至少一些代码片段(例如:更新数据库或确保文件写入完成)?@PincoPallino这取决于您的应用程序类型。但没有真正的保证。应用程序可能会崩溃或者机器可能会断电,这些操作没有任何原子性。