Web API - ConfigureAwait(true)无法正常工作

时间:2018-02-17 10:21:04

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

我在理解" continueOnCapturedContext"的内部和外部时遇到了一些麻烦。从.NET v4.6 WebAPI 2的角度来看。

我遇到的问题是在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);
    }

对于&#34; /测试?continueOnContext = true&#34;,这会让我回复:

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

所以你可以看到我有一个Sync上下文,我正在进行ConfigureAwait(true)但是线程还没有&#34; t&#34;继续&#34;以任何方式 - 在运行异步代码之前,运行之后分配新线程。这不是我期望的方式 - 我在这里有一些根本的误解吗?

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

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

2 个答案:

答案 0 :(得分:3)

  

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

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

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

答案 1 :(得分:2)

好的我明白了,所以我会发一个答案,以防它帮助别人。

在.NET 4.6 WebAPI 2中 - &#34;捕获的上下文&#34;我们继续不是线程,它是请求上下文。除其他外,Request Context知道HttpContext。当指定了ConfigureAwait(true)时,我们告诉.NET我们想要在等待之后保留请求上下文及其所有内容(HttpContext和其他一些属性) - 我们想要返回到我们开始的上下文与 - 这没有考虑到线程。

当我们指定ConfigureAwait(false)时,我们说我们不需要返回我们开始的请求上下文。这意味着.NET可以返回而不必关心HttpContext&amp;一些其他属性,因此边际性能增益。

鉴于知识我改变了我的代码:

    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)时,我们继续操作而不返回Request Context,因此HttpContext为null