【问题标题】:Web API - ConfigureAwait(true) not working as I thoughtWeb API - ConfigureAwait(true) 没有像我想象的那样工作
【发布时间】:2018-07-28 03:50:21
【问题描述】:

从 .NET v4.6 WebAPI 2 的角度来看,我在理解“continueOnCapturedContext”的输入和输出时遇到了一些麻烦。

我遇到的问题是 ConfigureAwait(true) 和 ConfigureAwait(false) 之间似乎没有任何区别。

我整理了一个示例应用程序来演示正在发生的事情:

    public async Task<IHttpActionResult> Get(bool continueOnContext)
    {
        int beforeRunningExampleThreadId = Thread.CurrentThread.ManagedThreadId;
        int runningExampleThreadId = await ExecuteExampleAsync(continueOnContext).ConfigureAwait(continueOnContext);
        int afterRunningExampleThreadId = Thread.CurrentThread.ManagedThreadId;

        return Ok(new
        {
            HasSyncContext = SynchronizationContext.Current != null,
            ContinueOnCapturedContext = continueOnContext,
            BeforeRunningExampleThreadId = beforeRunningExampleThreadId,
            RunningExampleThreadId = runningExampleThreadId,
            AfterRunningExampleThreadId = afterRunningExampleThreadId,
            ResultingCulture = Thread.CurrentThread.CurrentCulture,
            SameThreadRunningAndAfter = runningExampleThreadId == afterRunningExampleThreadId
        });
    }

    private async Task<int> ExecuteExampleAsync(bool continueOnContext)
    {
        return await Task.Delay(TimeSpan.FromMilliseconds(10)).ContinueWith((task) => Thread.CurrentThread.ManagedThreadId).ConfigureAwait(continueOnContext);
    }

对于“/Test?continueOnContext=true”,这会返回我:

{"HasSyncContext":true,"ContinueOnCapturedContext":true,"BeforeRunningExampleThreadId":43,"RunningExampleThreadId":31,"AfterRunningExampleThreadId":56,"ResultingCulture":"fr-CA","SameThreadRunningAndAfter":false}

所以你可以看到我有一个 Sync 上下文,我正在执行 ConfigureAwait(true),但线程并没有以任何方式“继续”——在运行异步代码之前、运行时和之后分配了一个新线程.这不是我所期望的方式 - 我在这里有一些基本的误解吗?

有人可以向我解释为什么在这段代码中 ConfigureAwait(true) 和 ConfigureAwait(false) 有效地做同样的事情吗?

更新 - 我想通了,并在下面回答。我也喜欢来自 @YuvalShap。如果您像我一样坚持这一点,我建议您同时阅读。

【问题讨论】:

标签: async-await asp.net-web-api2 .net-4.6.2 configureawait


【解决方案1】:

当异步处理程序在旧版 ASP.NET 上恢复执行时, 继续在请求上下文中排队。续作必 等待已排队的任何其他延续(仅 一次可以运行一个)。当它准备好运行时,会占用一个线程 从线程池中,进入请求上下文,然后继续 执行处理程序。 “重新进入”请求上下文涉及 一些内务处理任务,例如设置 HttpContext.Current 以及当前线程的身份和文化。

来自ASP.NET Core SynchronizationContext Stephen Cleary 的博文。

总而言之,Core 之前的 ASP.NET 版本使用 AspNetSynchronizationContext 作为请求上下文,这意味着当您调用 ConfigureAwait(true)(或不调用 ConfigureAwait(false))时,您会捕获告诉方法在请求上下文上恢复执行。 请求上下文保持 HttpContext.Current 和当前线程的身份和文化一致,但它不是特定线程独有的,唯一的限制是一次只能在上下文中运行一个线程。

【讨论】:

  • 感谢有关“管家任务”的额外信息 - 这解释了为什么在返回给调用者时会忽略在异步操作中设置线程文化。虽然我无法在指定 ConfigureAwait(false) 时复制它而不是“管家”——可能是因为线程在返回时正在切换。这个答案和我自己的完美总结。我会将此标记为答案,因为我认为标记我自己的形式很糟糕。
【解决方案2】:

好的,我想通了,所以我会发布一个答案,以防它对其他人有所帮助。

在 .NET 4.6 WebAPI 2 中 - 我们继续处理的“捕获的上下文”不是线程,而是请求上下文。除其他外,请求上下文知道 HttpContext。当指定 ConfigureAwait(true) 时,我们告诉 .NET 我们希望在等待之后保留请求上下文及其相关的所有内容(HttpContext 和一些其他属性)——我们想要返回到我们开始使用的上下文——这个不考虑线程。

当我们指定 ConfigureAwait(false) 时,我们是在说我们不需要返回到我们开始使用的请求上下文。这意味着 .NET 可以直接返回,而不必关心 HttpContext 和其他一些属性,因此会获得边际性能提升。

鉴于这些知识,我更改了我的代码:

    public async Task<IHttpActionResult> Get(bool continueOnContext)
    {
        var beforeRunningValue = HttpContext.Current != null;
        var whileRunningValue = await ExecuteExampleAsync(continueOnContext).ConfigureAwait(continueOnContext);
        var afterRunningValue = HttpContext.Current != null;

        return Ok(new
        {
            ContinueOnCapturedContext = continueOnContext,
            BeforeRunningValue = beforeRunningValue,
            WhileRunningValue = whileRunningValue,
            AfterRunningValue = afterRunningValue,
            SameBeforeAndAfter = beforeRunningValue == afterRunningValue
        });
    }

    private async Task<bool> ExecuteExampleAsync(bool continueOnContext)
    {
        return await Task.Delay(TimeSpan.FromMilliseconds(10)).ContinueWith((task) =>
        {
            var hasHttpContext = HttpContext.Current != null;
            return hasHttpContext;
        }).ConfigureAwait(continueOnContext);
    }

当 continueOnContext = true 时: {"ContinueOnCapturedContext":true,"BeforeRunningValue":true,"WhileRunningValue":false,"AfterRunningValue":true,"SameBeforeAndAfter":true}

当 continueOnContext = false 时: {"ContinueOnCapturedContext":false,"BeforeRunningValue":true,"WhileRunningValue":false,"AfterRunningValue":false,"SameBeforeAndAfter":false}

所以从这个例子你可以看到 HttpContext.Current 在异步方法之前存在,并且在异步方法期间丢失,无论 ConfigureAwait 设置如何。

区别在于异步操作完成后:

  • 当我们指定 ConfigureAwait(true) 时,我们将返回调用异步方法的请求上下文 - 这会进行一些内务处理并同步 HttpContext,因此当我们继续时它不为空
  • 当我们指定 ConfigureAwait(false) 时,我们只是继续而不返回请求上下文,因此 HttpContext 为空

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多