使用ConfigureAwait进行C#async / await链接(false)

基于众多书籍和博客,包括这里的优秀书籍,很明显当一个人写一个暴露助手异步方法的DLL库,即包装器方法时,通常认为内部完成实际异步方法的I / O任务的最佳实践在这样的线程池线程上(为了简洁起见,下面显示了伪代码,我以HttpClient为例)

 public Async Task MyMethodAsync(..) { ... var httpClient = new HttpClient(..); var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false); ... return response; } 

这里的关键是使用ConfigureAwait(false)以便在线程池线程上而不是在原始线程上下文上发生IO任务完成,从而可能防止死锁。

我的问题是从来电者的角度来看。 我对调用者和上面的方法调用之间存在多层方法调用的情况特别感兴趣,如下例所示。

 CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync 

仅对最终方法使用ConfigureAwait(false)是否足够,还是应该确保Method1AsyncMethod2Async还在内部使用ConfigureAwait(false)调用其异步方法? 将它包含在所有这些中间方法中似乎很愚蠢,特别是如果Method1AsyncMethod2Async只是最终调用MyMethodAsync重载。 有任何想法,请指教!

更新了示例所以如果我有一个包含以下私有异步方法的库,

 private async Task MyPrivateMethodAsync(MyClass myClass) { ... return await SomeObject.ReadAsStringAsync().ConfigureAwait(false); } 

我应该确保以下公共重载方法还包括ConfigureAwait(false),如下所示?

 public async Task MyMethodAsync(string from) { return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false); } public async Task MyMethodAsync(string from, string to) { return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false); } 

当然不。 ConfigureAwait就像它的名字建议配置await 。 它只影响await它的await

ConfigureAwait实际上返回一个不同的等待类型, ConfiguredTaskAwaitable而不是Task ,它反过来返回一个不同的awaiter类型ConfiguredTaskAwaiter而不是TaskAwaiter

如果要忽略所有awaitSynchronizationContext ,则必须对每个await使用ConfigureAwait(false)

如果要限制使用ConfigureAwait(false) ,可以在最顶层使用我的NoSynchronizationContextScope (请参阅此处 ):

 async Task CallerA() { using (NoSynchronizationContextScope.Enter()) { await Method1Async(); } } 

等待任务时,它会创建一个相应的TaskAwaiter来跟踪同时捕获当前SynchronizationContext的任务。 任务完成后,awaiter在捕获的上下文上等待(称为延续)之后运行代码。

您可以通过调用ConfigureAwait(false)来阻止它,这会创建一种不同类型的可启动( ConfiguredTaskAwaitable )及其相应的awaiter( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ),它不会在捕获的上下文上运行continuation。

关键是,对于每个await ,创建一个不同的awaiter实例,它不是在方法或程序中所有等待的东西之间共享的东西。 因此,最好为每个await语句调用ConfigureAwait(false)

您可以在此处查看等待者的源代码。