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

时间:2015-02-28 00:03:49

标签: c# .net task-parallel-library async-await io-completion-ports

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

public Async Task<HttpResponseMessage> 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<string> MyPrivateMethodAsync(MyClass myClass)
{
    ...
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false);
}

我应该确保以下公共重载方法都包含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);
}

2 个答案:

答案 0 :(得分:11)

绝对不是。 ConfigureAwait就像它的名字建议配置await一样。它只影响await加上它。

ConfigureAwait实际上会返回一个不同的等待类型,ConfiguredTaskAwaitable而不是Task,后者又会返回不同的等待类型ConfiguredTaskAwaiter,而不是TaskAwaiter

如果您想忽略所有SynchronizationContext的{​​{1}},则必须为每个await使用ConfigureAwait(false)

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

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

答案 1 :(得分:4)

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

您可以通过调用ConfigureAwait(false)来阻止这种情况,ConfiguredTaskAwaitable创建一种不同类型的可启动(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter)及其相应的等待者(await),它不会在捕获的上下文中运行延续

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

您可以看到等待者here的源代码。