首页 > 其他分享 >dotnet 使用 ConfigureAwait.Fody 库设置默认的 await 同步上下文切换配置

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

时间:2022-09-21 08:57:53浏览次数:114  
标签:异步 Fody await ConfigureAwait 线程 UI

在 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 方法,通过此工具自动加上

标签:异步,Fody,await,ConfigureAwait,线程,UI
From: https://www.cnblogs.com/lindexi/p/16714343.html

相关文章

  • dotnet 使用 TaskTupleAwaiter 同时等待多个任务简化代码写法
    在某些业务逻辑下,需要同时等待多个任务执行完成,才能继续往下执行后续逻辑。等待任务执行的逻辑,大部分情况下需要使用到Task.WhenAll方法,代码行数不少。另外,在需要获取多......
  • 获取请求; .then 和 async/await 的区别
    获取请求;.then和async/await的区别javascript中的异步代码可能会令人困惑、棘手且难以理解。但是,异步代码使我们的程序更加高效、易用,现代网页完全依赖它!作为开发人......
  • MAUI页面导航-await Shell.Current.GoToAsync();
    示例:Shell.Current.GoToAsync("..");//导航到前一页Shell.Current.GoToAsync(nameof(NotePage));//导航到Note页Shell.Current.GoToAsync($"{nameof(NotePage)}?{......
  • EventLoop中的async和await
    直接先来看一道题:asyncfunctionasync1(){console.log('async1start')awaitasync2()console.log('async1end')}asyncfunctionasync2(){console.lo......
  • JS: 模拟async/await语法糖
    不熟悉生成器对象的小伙伴,可查看:Generator、Generator.prototype.next模拟函数:/***模拟async关键字的函数*(不返回Promise对象也是可以的)*@paramgenerator*......
  • js四种异步方法(回调函数、Promise、Generator、async/await)
    由于JS运行环境是单线程的,即一次只能完成一个任务,所以多任务时需要排队。异步可以理解为改变执行顺序的操作,异步任务必须在同步任务执行结束之后,从任务队列中依次取出执行......
  • async-await
    async函数介绍  1、async函数执行结果是:返回一个Promise对象(newPromise) fn返回普通值(只要不抛错返回的promise的状态就是fulfilled)asyncfunctionf......
  • 第 9 题:Async/Await 如何通过同步的方式实现异步
    首先想要更好的理解Async/Await,需要了解这两个知识点:同步异步背景首先,js是单线程的(重复三遍),所谓单线程,通俗的讲就是,一根筋(比喻有点过分,哈哈)执行代码是一行一行的往......
  • 第 8 题:setTimeout、Promise、Async/Await 的区别
    1.setTimeoutconsole.log('scriptstart')//1.打印scriptstartsetTimeout(function(){console.log('settimeout')//4.打印settimeout})//2.......
  • Rust 如何实现 async/await
    目录FutureWake&Context为什么需要executor?什么是waker?async/awaitExecutorWakerstruct到ArcWaketraitFuturesUnordered单线程executor线程池executor总结异......