【问题标题】:Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?库中的 ConfigureAwait(false) 是否会丢失调用应用程序的同步上下文?
【发布时间】:2015-09-12 19:31:21
【问题描述】:

我已经多次阅读比我聪明的人的建议,其中几乎没有警告:始终在库代码中使用ConfigureAwait(false)。所以我相当肯定我知道答案,但我想成为 100%。场景是我有一个库,它轻薄地包装了一些其他异步库。

库代码:

public async Task DoThingAsyc() {
    // do some setup
    return await otherLib.DoThingAsync().ConfigureAwait(false);
}

应用代码:

// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?

【问题讨论】:

  • ConfigureAwait 不返回 Task,而是返回 ConfiguredTaskAwaitableDoThingAsync 看起来不像示例中的那样。
  • @i3arnon 所以我想这甚至都不会建成。现在好点了? (我的 atm 上没有编译器)
  • 是的,可以编译,ConfigureAwait 不会影响调用者。
  • 感谢您的帮助。鉴于此,我认为问题变成“应该”我做出这个改变,我已经单独问过here

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


【解决方案1】:

没有。

SynchronizationContext 的捕获发生在 awaitConfigureAwait 配置具体的await

如果应用程序调用库的异步方法并等待它,则无论调用内部发生什么,都会立即捕获 SC。

现在,因为 async 方法的同步部分(即第一个 await 之前的部分)在任务返回等待之前执行,所以您可以在那里乱用 SynchronizationContext,但 ConfigureAwait 不会不要那样做。


在您的具体示例中,您似乎正在从 async 方法返回 ConfigureAwait 的结果。这不可能发生,因为ConfigureAwait 返回ConfiguredTaskAwaitable 结构。但是,如果我们更改方法返回类型:

public ConfiguredTaskAwaitable DoThingAsyc() 
{
    return otherLib.DoThingAsync().ConfigureAwait(false);
}

那么等待它确实会影响调用代码的等待行为。

【讨论】:

  • 我不是说你错了,但我觉得这部分令人困惑:ConfigureAwait 配置特定的await。库的Task DoThingAsyc 是一个同步方法,它返回一个任务并且不使用await。那么这是否意味着ConfigureAwait(false)确实会影响用户的await,因为用await myLib.DoThingAync();调用这样的同步方法与直接调用await otherLib.DoThingAsync().ConfigureAwait(false);没有什么不同,这应该 i> 根据该措辞影响await
【解决方案2】:

来自http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx的示例

...逻辑上你可以想到以下代码:

await FooAsync();
RestOfMethod();

本质上与此相似:

var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
    if (currentContext == null)
        RestOfMethod();
    else
        currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);

这意味着您应该在await myLib.DoThingAync(); 调用之后恢复您的上下文。

【讨论】:

    【解决方案3】:

    如果在异步调用的逻辑链中使用不一致,ConfigureAwait(false) 可能会添加冗余上下文切换(这通常意味着冗余线程切换)。这可能发生在存在同步上下文的情况下,当逻辑堆栈上的一些异步调用使用ConfigureAwait(false) 而有些不使用(更多here)。

    您仍应在代码中使用 ConfigureAwait(false),但您可能希望查看您正在调用的第 3 方代码并通过以下方式缓解任何不一致:

    public async Task DoThingAsyc() {
        // do some setup
        await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
        // do some other stuff
    }
    

    这会增加一个额外的线程切换,但可能会阻止许多其他线程切换。

    此外,如果您要创建一个像您展示的那样非常薄的包装器,您可能希望像下面这样实现它,根本不需要 async/await

    public Task DoThingAsyc() {
        // do some setup
        return otherLib.DoThingAsync();
    }
    

    【讨论】:

    • 您在底部的示例正是我的library 今天在几十个地方的做法,问题的关键是我是否应该将这些调用更改为使用ConfigureAwait(false)。正如@i3arnon 提醒我的那样,ConfigureAwait 返回一个ConfiguredTaskAwaitable,我需要将其包装回Task,这可能会带来一些 开销。再加上你提到的潜在线程切换让我想知道,这值得还是我应该保持原样?
    • 其实我认为这值得new question
    • @ToddMenier,基本上你只需要await,如果你有一个延续代码(如// do some other stuffabove)。否则,使用它是没有意义的,除非您特别想要异常终止的 async 语义(更多 here)。
    猜你喜欢
    • 1970-01-01
    • 2017-06-06
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 1970-01-01
    • 2019-04-26
    • 2023-03-10
    • 2019-08-27
    相关资源
    最近更新 更多