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

Sam*_*evx 9 .net c# task-parallel-library async-await io-completion-ports

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

public Async Task<HttpResponseMessage> MyMethodAsync(..)
{
    ...
    var httpClient = new HttpClient(..);
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false);
    ...
    return response;
}
Run Code Online (Sandbox Code Playgroud)

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

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

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync
Run Code Online (Sandbox Code Playgroud)

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

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

private async Task<string> MyPrivateMethodAsync(MyClass myClass)
{
    ...
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

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

public async Task<string> MyMethodAsync(string from)
{
        return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false);
}
public async Task<string> MyMethodAsync(string from, string to)
{
        return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

i3a*_*non 12

当然不.ConfigureAwait正如它的名字建议配置await.它只会影响它的await耦合.

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

如果你想忽视SynchronizationContextawait的所有你必须ConfigureAwait(false)为他们每个人使用.

如果你想限制你的使用,ConfigureAwait(false)可以在最顶层使用我的NoSynchronizationContextScope(见这里):

async Task CallerA()
{
    using (NoSynchronizationContextScope.Enter())
    {
        await Method1Async();
    }
}
Run Code Online (Sandbox Code Playgroud)


Ned*_*nov 5

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

您可以通过调用 来防止这种情况发生ConfigureAwait(false),这会创建一种不同类型的 awiatable ( ConfiguredTaskAwaitable) 及其相应的 awaiter ( ConfiguredTaskAwaitable.ConfiguredTaskAwaiter),它不会在捕获的上下文上运行延续。

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

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