有没有办法将.ConfigureAwait(false)应用于整个任务子树?

时间:2016-09-17 12:22:06

标签: c# .net asynchronous async-await

我有一个并行运行数千个异步任务的类,它远远大于工作线程的数量。如果我在等待呼叫时没有做.ConfigureAwait(false)那么我的性能要低得多,但将.ConfigureAwait(false)附加到每个等待呼叫都很繁琐并降低了代码的可读性。我正在寻找一种方法来在一个产生这些任务的函数中指定某种类型的空上下文,这样每个等待它们内部的调用都不会自动关注SynchronizationContext。有可能吗?

更新:我已经完成了一些谷歌搜索,看起来一旦我已经在.ConfigureAwait(false)的函数中,当前的SynchronizationContext将为null并且我不会# 39;在子函数调用中需要关心它。这是对的吗?

2 个答案:

答案 0 :(得分:3)

是的,你可以。通过重置当前的SynchronizationContext。

在顶级异步方法中执行此操作:

async Task TopLevelAsync()
{
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);
    try
    {
        // no need for ConfigureAwait(false)
        await SubTask1Async();
        await SubTask2Async();
    }
    finally
    {
         SynchronizationContext.SetSynchronizationContext(syncContext);
    }
}
  

一旦我已经在.ConfigureAwait(false)函数中,当前的SynchronizationContext将为null,我不需要在子函数调用中关心它。这是对的吗?

不幸的是,事实并非如此。这是一个危险的假设。这就是为什么:

// in a top-level async method..
await FooAsync().ConfigureAwait(false);

async Task FooAsync()
{
    var result = await BarAsync().ConfigureAwait(false);
    // we are inside the ConfigureAwait(false), or in the
    // continuation after ConfigureAwait(false), so at this
    // point the SynchronizationContext must be null, right?
    // No it's not.
}

Task<bool> BarAsync()
{
    return Task.FromResult(true);
}

从上面的示例BarAsync同步完成,这意味着FooAsync方法后面的状态机会立即继续执行,而无需重新调度。如果没有重新调度逻辑,ConfigureAwait(false)不会进入帐户,因此从不重置SynchronizationContext。

答案 1 :(得分:2)

  

看起来我曾经在一个带有.ConfigureAwait(false)的函数中,当前的SynchronizationContext将为null,我不需要在子函数调用中关心它。这是对的吗?

嗯,实际的情况是:

  • 您在异步功能中。
  • 你开始一项任务。
  • 在该任务完成之前,您执行了await(配置了false)。

所以,依赖它并不是一个好的情况。具体来说,如果任务快速完成(在您配置的await被命中之前),那么您仍将处于原始上下文中。请注意,即使您没有预料到这种情况,也会发生这种情况;例如,移动设备对缓存Web请求非常积极。

To&#34;走出&#34;同步上下文,只需将其包装在Task.Run中 - 这非常简单地使用线程池线程,并执行该线程池上下文中的所有后代代码。