博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ConfigureAwait 干了啥?

Posted on 2023-05-29 08:55  qianyz  阅读(66)  评论(0编辑  收藏  举报

本文引用:

异步编程之Async,Await和ConfigureAwait的关系 - Leon_Chaunce - 博客园 (cnblogs.com)

走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享 - 博客园团队 - 博客园 (cnblogs.com)

 

ConfigureAwait(false)能做什么呢?

默认情况下,当您使用async/await时,它将在开始请求的原始线程上继续运行(状态机)。但是,如果当前另一个长时间运行的进程已经接管了该线程,那么你就不得不等待它完成。要避免这个问题,可以使用ConfigureAwait的方法和false参数。当你用这个方法的时候,这将告诉Task它可以在任何可用的线程上恢复自己继续运行,而不是等待最初创建它的线程。这将加快响应速度并避免许多死锁。

但是,这里有一点点损失。当您在另一个线程上继续时,线程同步上下文将丢失,因为状态机改变。这里最大的损失是你会失去归属于线程的Culture和Language,其中包含了国家语言时区信息,以及来自原始线程的HttpContext.Current之类的信息,因此,如果您不需要以此来做多语系或操作任何HttpContext类型设置,则可以安全地进行此方法的调用。注意:如果需要language/culture,可以始终在await之前存储当前相关状态值,然后在await新线程之后重新应用它。

 

 

1)当ConfigureAwait(true),代码由同步执行进入异步执行时,当前同步执行的线程上下文信息(比如HttpConext.Current,Thread.CurrentThread.CurrentCulture)就会被捕获并保存至SynchronizationContext中,供异步执行中使用,并且供异步执行完成之后(await之后的代码)的同步执行中使用(虽然await之后是同步执行的,但是发生了线程切换,会在另外一个线程中执行「ASP.NET场景」)。这个捕获当然是有代价的,当时我们误以为性能问题是这个地方的开销引起,但实际上这个开销很小,在我们的应用场景不至于会带来性能问题。

2)当Configurewait(flase),则不进行线程上下文信息的捕获,async方法中与await之后的代码执行时就无法获取await之前的线程的上下文信息,在ASP.NET中最直接的影响就是HttpConext.Current的值为null。

我们在犯傻过程中,工作量最大的就是处理HttpConext.Current为null的情况。

由于之前写代码时的幼稚与偷懒,造成了很多地方用HttpConext.Current去获取http请求相关信息。在异步化改造之后,HttpConext.Current也遍布在很多aync方法中。Configurewait(flase)之后,NullReferenceException如雨后春笋。

针对这样的窘境,我们只能一个个修改代码,通过方法参数传递所需要的HttpConext信息,取代原先的HttpConext.Current“绿色通道”访问方式。

 

说了这么多,我们来个个例子(例子来自网络方便复习的时候用(14条消息) C# 多线程九 任务Task的简单理解与运用三_task.fromexception_一梭键盘任平生的博客-CSDN博客

private void button2_Click(object sender, EventArgs e) {
        Debug.Print($"创建task的线程(主线程):{Thread.CurrentThread.ManagedThreadId}");
        Task task = Task.Run(() => {
            Debug.Print($"主任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
        task.ContinueWith((obj) => {
            Debug.Print($"附加任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
        ConfiguredTaskAwaitable taskAwaitable = task.ConfigureAwait(true);
        taskAwaitable.GetAwaiter().OnCompleted(() => {
            Debug.Print($"后续任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
    }

 

打印:

 我们可以看到当task.ConfigureAwait(true)的时候 taskAwaitable.GetAwaiter().OnCompleted持有的委托使用的是线程1 既是创建task的线程 

 

下边是修改未false之后的

 

下边是修改未false之后的
private void button2_Click(object sender, EventArgs e) {
        Debug.Print($"创建task的线程(主线程):{Thread.CurrentThread.ManagedThreadId}");
        Task task = Task.Run(() => {
            Debug.Print($"主任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
        task.ContinueWith((obj) => {
            Debug.Print($"附加任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
        ConfiguredTaskAwaitable taskAwaitable = task.ConfigureAwait(false);
        taskAwaitable.GetAwaiter().OnCompleted(() => {
            Debug.Print($"后续任务线程:{Thread.CurrentThread.ManagedThreadId} ");
        });
    }

 

打印: