在 dotnet 里面,使用 await 进行异步逻辑,默认是会尝试切换回调用 await 的线程同步上下文。这个机制对于大多数的上层应用来说都是符合逻辑且方便的逻辑,例如对于带 UI 线程的 WPF 或 WinForms 等应用,基础开发的执行逻辑基本都是在 UI 线程上,此时进入一次 await 再出来,期望如果是进入 await 之前是在 UI 线程,那么执行 await 完成之后,退出的代码也能在 UI 线程执行,正好这就是 dotnet 的默认行为。但是对于库开发者来说,情况就反过来的,库的开发者大部分时候更期望默认不要切换回调用方的线程,采用 Fody 的 ConfigureAwait.Fody 库,可以控制此默认的行为。本文将告诉大家如何使用 ConfigureAwait.Fody 库

这是一个在 GitHub 上使用最友好的 MIT 协议开源的库,请看 https://github.com/Fody/ConfigureAwait

用 Fody 的 ConfigureAwait.Fody 库,可以控制 await 在结束之后的切换同步上下文默认的行为,对于库的开发者来说相对比较方便。大部分的库的逻辑,都是期望在异步之后,不要明确切换回原调用方的线程,因为切换回原调用方的线程存在很多不可控逻辑。例如在 WPF 里面,需要通过 Dispatcher 调度,如此会让 UI 线程过于繁忙。而且切换调度逻辑,可能出现和原有线程相互等待的情况

例如 UI 线程进入了 Wait 逻辑,等待异步执行完成。然而异步执行完成的最后一步是做切换线程同步上下文,切换到 UI 线程。大家可以看到,异步的最后一步是在等待 UI 线程切换,相当于在 WPF 里面使用 Dispatcher 调度,然而 UI 线程却进入了 Wait 方法,也就是 UI 线程在异步完成之后无法进行调度。此时的异步在等待 UI 调度,而 UI 在等待异步完成。如此将会锁住 UI 线程

详细请看

根据 walterlv 大佬的 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁 - walterlv 博客,可以了解到,在库里面,如果不关心线程本身,例如代码不需要在 WPF 的 UI 线程执行,可以采用 ConfigureAwait(false) 的方式避免使用者死锁

原因在于在 await 完成前,可以采用 ConfigureAwait 配置异步的最后一步是否需要尝试切换回原有的线程。默认是 true 的值,表示需要。如果加上了 ConfigureAwait 函数,设置 false 的值,那就表示不要切换回原有的线程。此时如果业务端在 UI 线程使用 Wait 等方法,那依然是安全的,原因是 UI 线程在等待异步完成,然而异步完成不需要调度回 UI 线程,可以由线程池选择线程调度,于是异步的完成不需要等待 UI 线程,能够让 UI 线程等待异步完成

那引入的问题就来了,在库里面,将会让 await 异步逻辑充满了 ConfigureAwait(false) 的代码,如此将会让代码不好看。用 Fody 的 ConfigureAwait.Fody 库就是用来解决此问题的,可以配置默认行为,例如 dotnet 里面默认是用的是 true 的值,对于库的代码,可以反过来,配置默认是 false 的值,可以减少大量的代码

按照 dotnet 的使用惯例,第一步就是先安装 NuGet 库。由于 ConfigureAwait.Fody 库是 Fody 库的扩展,请同时安装 ConfigureAwait.FodyFody

使用方法很灵活,可以配置整个程序集的默认行为,也可以只配置某个类或类里面某个方法的默认行为。配置整个程序集的默认行为代码如下,添加程序集的特性即可

[assembly: Fody.ConfigureAwait(false)]

对于某个类或类里面某个方法的默认行为的配置,可以给类或方法加上如下特性

[Fody.ConfigureAwait(false)]

例子如下

        [Fody.ConfigureAwait(false)]
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"ThreadId={Thread.CurrentThread.ManagedThreadId}"); // 输出 1
await Task.Delay(100);
Debug.WriteLine($"ThreadId={Thread.CurrentThread.ManagedThreadId}"); // 输出 2
}

如此即可配置行为为加上 ConfigureAwait(false) 不尝试切换回原因的线程同步上下文

按照 Fody 的使用方法,加上 FodyWeavers.xml 文件,在 FodyWeavers.xml 文件里面开启 ConfigureAwait.Fody 的功能

<Weavers>
<ConfigureAwait/>
</Weavers>

如果想对程序集做默认配置,也可以不写程序集特性,可以通过在 FodyWeavers.xml 文件里设置默认值的方式实现

<Weavers>
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

实现原理是编译器优化,如原本的代码如下

using Fody;

[ConfigureAwait(false)]
public class MyAsyncLibrary
{
public async Task MyMethodAsync()
{
await Task.Delay(10);
await Task.Delay(20);
} public async Task AnotherMethodAsync()
{
await Task.Delay(30);
}
}

将会编译生成大概如下等价代码

public class MyAsyncLibrary
{
public async Task MyMethodAsync()
{
await Task.Delay(10).ConfigureAwait(false);
await Task.Delay(20).ConfigureAwait(false);
} public async Task AnotherMethodAsync()
{
await Task.Delay(30).ConfigureAwait(false);
}
}

相当于不需要开发者手动加上 ConfigureAwait 方法,通过此工具自动加上

dotnet 使用 ConfigureAwait.Fody 库设置默认的 await 同步上下文切换配置的更多相关文章

  1. IIS设置默认主页无效

    服务器系统:Windows server 2008 R2 IIS版本:7.5 IIS中部署一个dotnet framework 3.5的网站应用程序,设置"默认文档"为:index ...

  2. 实例讲解Oracle数据库设置默认表空间问题

    实例讲解Oracle数据库设置默认表空间问题   实例讲解Oracle数据库设置默认表空间问题,阅读实例讲解Oracle数据库设置默认表空间问题,DBA们经常会遇到一个这样令人头疼的问题:不知道谁在O ...

  3. 我的Android进阶之旅------>Android 设置默认语言、默认时区

    1. 设置默认时区 PRODUCT_PROPERTY_OVERRIDES += \ persist.sys.timezone=Asia/Shanghai\ 注:搜索“persist.sys.timez ...

  4. 设置默认Browser

    电信A库要求android系统中有多个Browser时,开机自动设置一个默认浏览器,而不用弹出选择框让用户手动选择. 监听开机广播Intent.ACTION_BOOT_COMPLETED, 用Pack ...

  5. 微软更新导致的IIS7设置默认主页无效

    近期两个superKM的老客户出现问题,网站不能自动检索默认文档,必须通过完整网址才能访问. 值得一提的是出现问题的都是 IIS7 和7.5版本,服务器为windows server2008 R2. ...

  6. MTK Android中设置默认时区

    设置默认时区 PRODUCT_PROPERTY_OVERRIDES += \ persist.sys.timezone=Asia/Shanghai\ 注:搜索“persist.sys.timezone ...

  7. datepickerx设置默认日期

    datepicher插件是jQuery UI的一个插件,它提供一个日期弹出窗口(或直接显示在页面),供用户选择日期.在Web开发中,总会遇到需要用户输入日期的情况.一般都是提供一个text类型的inp ...

  8. .NET DateTime类型变量作为参数时设置默认值

    一个小的 Tips. .NET 中函数参数的默认值需要是编译时常量.如果参数是引用类型,可以设置Null,如果是值类型,可以设置相应的编译时常量,如整型可以用整数,但对于DateTime(结构体,值类 ...

  9. ng-option指令使用记录,设置默认值需要注意

    ng-options一般有以下用法: 数组作为数据源: label for value in array select as label for value in array label group ...

  10. 《Entity Framework 6 Recipes》中文翻译系列 (14) -----第三章 查询之查询中设置默认值和存储过程返回多结果集

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-6在查询中设置默认值 问题 你有这样一个用例,当查询返回null值时,给相应属性 ...

随机推荐

  1. App启动页面优化

    目录介绍 01.存在白屏问题 1.1 问题描述 1.2 问题分析 02.解决白屏的办法 2.1 解决方案分析 2.2 第一种解决方案 2.3 第二种解决方案 2.4 注意要点 03.Applicati ...

  2. 开源一个教学型分库分表示例项目 shardingsphere-jdbc-demo

    在笔者心中,消息队列,缓存,分库分表是高并发解决方案三剑客. 分库分表之所以被广泛使用,因为工程相对简单,但分库分表并不仅仅是分片,还是需要考虑如何扩缩容(全量同步.增量同步.数据校验等). 因此笔者 ...

  3. Swift Structured Concurrency

    异步函数 异步函数概念 异步和并发是两个不同的概念,并发(Concurrency)是指多个任务同时执行,这里的同时不是严格意义上的同一时刻,而是在稍大时间粒度上,多个任务可以同时推进,并发的实现可以是 ...

  4. SwiftUI 笔记

    TextField 监听 lost focus 之前有一个初始化方法,传入一个 onEditingChanged closure,但这个方法废弃了,文档中也说了 alternative:使用 Focu ...

  5. 补充--关于nginx服务器多个网站如何设置404的问题?

    补充--关于nginx服务器多个网站如何设置404的问题? 需求1 :设置多个网站404页面为一个 都需配置网站的nginx.conf,以上面的多网站为例,404发布目录下,每个的nginx.conf ...

  6. vue3.0后多环境配置

    根目录下创建 .env 每个配置文件中都将包含此文件中的数据,类似于配置文件的全局 .env.development 默认开发环境 对应serve .env.production 默认生产环境 对应b ...

  7. 攻防世界 gametime 使用IDA pro+OD动调

    自学犟种琢磨动调的一个记录,算是第一次动调的新手向,大佬请飘过 题目 准备工作--IDA pro(32X) 下载得到一个exe文件,首先丢到PE里面--无壳,32bit 丢到IDA pro(x32)里 ...

  8. C++ 通用锁管理

    lock_guard 类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制. 创建 lock_guard 对象时,它试图接收给定互斥的所有权.控制离开创建 l ...

  9. #数位dp,高精度#洛谷 2235 [HNOI2002]Kathy函数

    题目 分析 首先这个\(f\)函数其实求的是二进制下的回文数,简单证明一下 设\(n\)在二进制下的回文数为\(n'\),第一二条显然 第三条\(f(2n)=f(n)\Rightarrow \over ...

  10. Python制作词云--stylecloud简单使用

    安装 pip install stylecloud 使用 from stylecloud import gen_stylecloud gen_stylecloud('zhangsan lisi wan ...