首页 > 编程语言 >C#中CancellationToken和CancellationTokenSource用法

C#中CancellationToken和CancellationTokenSource用法

时间:2023-12-18 14:13:29浏览次数:42  
标签:CancellationToken C# CancellationTokenSource Token new cancellationTokenSource p

C#中CancellationToken和CancellationTokenSource用法

 

  之前做开发时,一直没注意这个东西,做了.net core之后,发现CancellationToken用的越来越平凡了。

  这也难怪,原来.net framework使用异步的不是很多,而.net core首推异步编程,到处可以看到Task的影子,而CancellationToken正好是异步Task的一个控制器!所以花点时间做个笔记

  

  CancellationToken

  CancellationToken有一个构造函数,可以传入一个bool类型表示当前的CancellationToken是否是取消状态。另外,因为CancellationToken是一个结构体,所以它还有一个空参数的构造函数。  

    public CancellationToken();//因为是结构体,才有空构造函数,不过没什么作用
    public CancellationToken(bool canceled);

  属性如下:  

    //静态属性,获取一个空的CancellationToken,这个CancellationToken注册的回调方法不会被触发,作用类似于使用空构造函数得到的CancellationToken
    public static CancellationToken None { get; }
    //表示当前CancellationToken是否可以被取消
    public bool CanBeCanceled { get; }
    //表示当前CancellationToken是否已经是取消状态
    public bool IsCancellationRequested { get; }
    //和CancellationToken关联的WaitHandle对象,CancellationToken注册的回调方法执行时通过这个WaitHandle实现的
    public WaitHandle WaitHandle { get; }

  常用方法:  

    //往CancellationToken中注册回调
    public CancellationTokenRegistration Register(Action callback);
    public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext);
    public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state);
    public CancellationTokenRegistration Register([NullableAttribute(new[] { 1, 2 })] Action<object?> callback, object? state, bool useSynchronizationContext);
    //当CancellationToken处于取消状态是,抛出System.OperationCanceledException异常
    public void ThrowIfCancellationRequested();

  常用的注册回调的方法是上面4个Register方法,其中callback是回调执行的委托,useSynchronizationContext表示是否使用同步上下文,state是往回调委托中传的参数值

  另外,Register方法会返回一个CancellationTokenRegistration结构体,当注册回调之后,可以调用CancellationTokenRegistration的Unregister方法来取消注册,这个Unregister方法会返回一个bool值,当成功取消时返回true,当取消失败(比如回调已执行)将返回false:  

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var cancellationTokenRegistration = cancellationTokenSource.Token.Register(() =>
    {
        Console.WriteLine("Canceled");//这里将不会执行输出
    });

    //cancellationTokenSource.Cancel();
    //var result = cancellationTokenRegistration.Unregister();//result = false

    var result = cancellationTokenRegistration.Unregister();//result = true
   cancellationTokenSource.Cancel();

   上面提到,CancellationToken可以使用构造函数直接构造,同时可以传入一个参数,表示当前的状态,需要注意的是,CancellationToken的状态最多可以改变一次,也就是从未取消变成已取消。

  如果构造时传入true,也就是说CancellationToken是已取消状态,这个时候注册的回调都会立即执行:  

    CancellationToken cancellationToken = new CancellationToken(true);
    cancellationToken.Register(() =>
    {
        Console.WriteLine("Canceled");//这里会立即执行输出Canceled
    });

  但如果构造时传入的是false,说明CancellationToken处于未取消状态,这时候注册的回到都会处于一个待触发状态:  

    CancellationToken cancellationToken = new CancellationToken(false);
    cancellationToken.Register(() =>
    {
        Console.WriteLine("Canceled");//这里不会立即执行输出
    });

  通过Register方法注册的服务只会执行一次!

  但一般的,如果传入false构造出来的CancellationToken,可以认为是不会触发的,因为它没有触发的方法!所以一般的,我们都不会直接使用构造函数创建CancellationToken,而是使用CancellationTokenSource对象来获取一个CancellationToken

 

  CancellationTokenSource

  CancellationTokenSource可以理解为CancellationToken的控制器,控制它什么时候变成取消状态的一个对象,它有一个CancellationToken类型的属性Token,只要CancellationTokenSource创建,这个Token也会被创建,同时Token会和这个CancellationTokenSource绑定:  

    //表示Token是否已处于取消状态
    public bool IsCancellationRequested { get; }
    //CancellationToken 对象
    public CancellationToken Token { get; }

  可以直接创建一个CancellationTokenSource对象,同时指定一个时间段,当过了这段时间后,CancellationTokenSource就会自动取消了。

  CancellationTokenSource的取消有4个方法:  

    //立刻取消
    public void Cancel();
    //立刻取消
    public void Cancel(bool throwOnFirstException);
    //延迟指定时间后取消
    public void CancelAfter(int millisecondsDelay);
    //延迟指定时间后取消
    public void CancelAfter(TimeSpan delay);

  Cancel和两个CancelAfter方法没什么特别的,主要就是有一个延迟的效果,需要注意的是Cancel的两个重载之间的区别。

  首先,上面说道,CancellationToken状态只能改变一次(从未取消变成已取消),当CancellationToken时已取消状态时,每次往其中注册的回调都会立刻执行!当处于未取消状态时,注册进去的回调都会等待执行。

  需要注意的是,当在未取消状态下注册多个回调时,它们在执行时是一个类似栈的结构顺序,先注册后执行。

  而CancellationToken的Register可以注册多个回调,那他们可能都会抛出异常,throwOnFirstException参数表示在第一次报错时的处理行为.

  throwOnFirstException = true 表示立即抛出当前发生的异常,后续的回调将会取消执行  

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    try
    {
        cancellationTokenSource.Token.Register(() =>
        {
            throw new Exception("1");
        });
        cancellationTokenSource.Token.Register(() =>
        {
            throw new Exception("2");//不会执行
        });

        cancellationTokenSource.Cancel(true);
    }
    catch (Exception ex)
    {
        //ex is System.Exception("1")
    }

   throwOnFirstException = false 表示跳过当前回调的异常,继续执行生效的回调,等所有的回调执行完成之后,再将所有的异常打包成一个System.AggregateException异常抛出来!  

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    try
    {
        cancellationTokenSource.Token.Register(() =>
        {
            throw new Exception("1");
        });
        cancellationTokenSource.Token.Register(() =>
        {
            throw new Exception("2");
        });

        cancellationTokenSource.Cancel(false);//相当于cancellationTokenSource.Cancel()
    }
    catch (Exception ex)
    {
        //ex is System.AggregateException:[Exception("2"),Exception("1")]
    }

   CancellationTokenSource还可以与其它CancellationToken关联起来,生成一个新的CancellationToken,当其他CancellationToken取消时,会自动触发当前的CancellationTokenSource执行取消动作!  

    CancellationTokenSource cancellationTokenSource1 = new CancellationTokenSource();
    cancellationTokenSource1.Token.Register(() =>
    {
        Console.WriteLine("Cancel1");
    });
    CancellationTokenSource cancellationTokenSource2 = new CancellationTokenSource();
    cancellationTokenSource2.Token.Register(() =>
    {
        Console.WriteLine("Cancel2");
    });
    CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource1.Token, cancellationTokenSource2.Token);
    cancellationTokenSource.Token.Register(() =>
    {
        Console.WriteLine("Cancel");
    });

    //cancellationTokenSource1.Cancel(); //执行这个依次输出 Cancel    Cancel1
    cancellationTokenSource2.Cancel(); //执行这个依次输出 Cancel    Cancel2

 

  使用场景一

  当我们创建异步操作时,可以传入一个CancellationToken,当异步操作处于等待执行状态时,可以通过设置CancellationToken为取消状态将异步操作取消执行:  

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var task = new Task(() =>
    {
        Thread.Sleep(1500);//执行了2秒中代码 Console.WriteLine("Execute Some Code"); }, cancellationTokenSource.Token); task.Start();//启动,等待调度执行 //发现不对,可以取消task执行 cancellationTokenSource.Cancel(); Thread.Sleep(1000);//等待1秒 Console.WriteLine("Task状态:" + task.Status);//Canceled

   但是经常的,我们的取消动作可能不会那么及时,如果异步已经执行了,再执行取消时无效的,这是就需要我们自己在异步委托中检测了:  

    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    var task = new Task(() =>
    {
        Thread.Sleep(1500);//执行了2秒中代码
        cancellationTokenSource.Token.ThrowIfCancellationRequested();
        Console.WriteLine("Execute Some Code");
    }, cancellationTokenSource.Token);

    task.Start();//启动,等待调度执行

    Thread.Sleep(1000);////一段时间后发现不对,可以取消task执行
    cancellationTokenSource.Cancel();
    Thread.Sleep(1000);//等待1秒
    Console.WriteLine("Task状态:" + task.Status);//Canceled

  

  使用场景二

   有时,我们希望在触发某个时间后,可以执行某些代码功能,但是在异步环境下,我们不能保证那些要执行的代码是否已准备好了,比如我们有一个Close方法,当调用Close后表示是关闭状态,如果我们相当程序处于关闭状态时执行一些通知,一般的,我们可能是想到采用事件模型,或者在Close方法传入事件委托,或者采用一些诸如模板设计这样的模型去实现:   

    class Demo
    {
        public void Close(Action callback)
        {
            //关闭
            Thread.Sleep(3000);

            callback?.Invoke();//执行通知
        }
    }

  或者  

    class Demo
    {
        public event Action Callback;

        public void Close()
        {
            //关闭
            Thread.Sleep(3000);

            Callback?.Invoke();//执行通知
        }
    }

  但是这就有问题了,如果是传入参数或者采用事件模型,因为前面说过了,如果在异步环境下,我们不能保证那些要执行的代码是否已准备好了,也许在执行Close方法时,程序还未注册回调。

  这个时候就可以使用CancellationToken来解决这个问题:  

    class Demo
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        public CancellationToken Token { get => cancellationTokenSource.Token; }

        public void Close()
        {
            //关闭
            Thread.Sleep(3000);

            cancellationTokenSource.Cancel();//执行通知
        }
    }

  主需要往Token属性中注册回调而无需关注Close什么时候执行了

 

  使用场景三

  有时候,我们写一个异步无限循环的方法去处理一些问题,而我们希望可以在方法外来停止它这个时候,我们就可以通过返回CancellationTokenSource来实现了:  

        public CancellationTokenSource Listen()
        {
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

            //循环调度执行
            Task.Run(() =>
            {
                while (true)
                {
                    cancellationTokenSource.Token.ThrowIfCancellationRequested();

                    //循环执行一些操作
                    Thread.Sleep(1000);
                    Console.WriteLine("Run"); } }); return cancellationTokenSource; }

   

  使用场景四

  这种场景比较常见,它常将CancellationToken作为方法的参数,在方法内部可以往CancellationToken中注册回调委托,而CancellationToken的取消动作则是在方法外部触发,例如:

    static void Main(string[] args)
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        Method("hello", cancellationTokenSource.Token);
        cancellationTokenSource.Cancel();
    }
    static void Method(string message, CancellationToken cancellationToken = default)
    {
        Console.WriteLine($"message:{message}");
        cancellationToken.Register(() =>
        {
            Console.WriteLine($"complete");
        });
    }

  这样做的好处是,可以让方法内部产生的事件交给方法调用主流程去触发,而又不干扰方法调用主流程的逻辑,具有很好的可控制性。还可以传进来一个注册好回调的CancellationToken,在方法内部适当的位置去触发取消动作。目前,.netcore内部大量的方法采用了这种方式,比如IHostedService:  

    public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }

  StartAsync在应用启动前执行,StopAsync是在应用停止前执行

 

一个专注于.NetCore的技术小白

标签:CancellationToken,C#,CancellationTokenSource,Token,new,cancellationTokenSource,p
From: https://www.cnblogs.com/sexintercourse/p/17911088.html

相关文章

  • C#中ref关键字的用法总结
    C#中ref关键字的用法总结 ref表示引用的意思,C#中它有多种用法,这里简单总结一下:1、按引用传递参数具体可见:C#中的值传递与引用传递(in、out、ref)2、引用局部变量引用局部变量指的是在变量声明时使用ref关键字(或者使用refreadonly表示未只读),表示这个变......
  • 2023 年活力开源贡献者、开源项目揭晓|JeecgBoot 成功入选
    JeecgBoot是一个开源的企业级快速开发平台,它成功入选2023年度生态开源项目,这是对其十年坚持开源的实至名归的认可。作为一个开源项目,JeecgBoot在过去的十年里一直秉承着开放、共享、协作的理念,不断推动着开源社区的发展。其成功入选2023年度生态开源项目,无疑是对其在开源领......
  • (亲测)云原生之使用docker部署ZPan个人网盘系统
    一、ZPan介绍1.1ZPan简介ZPan是一个基于云存储的自托管云盘,用于自建私人网盘或企业网盘。1.2ZPan特点不受服务器带宽限制支持所有兼容S3协议的云存储支持文件和文件夹管理支持文件和文件夹共享(无需登录即可访问)支持文档预览和音视频播放支持多用户存储空间控制支持多种语言二、检......
  • (亲测)云原生之使用Docker部署Teedy轻量级文档管理系统
    一、Teedy介绍1.1Teedy简介Teedy是一个开源的、功能丰富、易于使用和自定义的文档管理工具,它能够帮助用户管理和组织文档,适用于个人、小组和组织使用。1.2Teedy特点创建和编辑文档:用户可以使用Markdown格式创建和编辑文档,还可以添加标签和注释。文件上传和管理:用户可以上传和管理......
  • 关于DVWA靶场File Inclusion中出现The PHP function allow_url_include is not enable
    做实验时发现fileinclusion报错:ThePHPfunctionallow_url_includeisnotenabled,翻译一下是PHP函数allow_url_include还未启用,这时我想到之前好像配置DVWA靶场环境时也用到了这个第一反应我跑去查看PHP配置文件php.ini纳尼?!allow_url_include=on啊,明明已经开了啊剑锋一转难不成......
  • 关于DC电源模块有哪些常见的输入和输出参数?
    关于DC电源模块有哪些常见的输入和输出参数?BOSHIDADC电源模块是一种用于将交流电转换为直流电的设备,广泛应用于电子设备、通信设备、工业控制、仪器仪表等各个领域。在选择和使用DC电源模块时,了解其常见的输入和输出参数是很重要的。输入参数是指DC电源模块的工作电压范围和电流......
  • SpringBoot 2项目中,serviceA的方法中调用了serviceB的方法能触发事务吗
    需求描述在一个SpringBoot2.x项目中,有一个需求是在serviceA的method1方法中执行两个数据库操作,并在其中的某一步调用了serviceB的method2,而method2也包含了数据库操作。如果在这个过程中发生了异常,希望能够确保所有的数据库操作都能够回滚,以保持数据的一致性。该如何在代码中实现......
  • Debian和Ubuntu国内安装docker和docker-compose
    01-安装docker0、如果你过去安装过docker,先删掉sudoapt-getremovedockerdocker-enginedocker.iocontainerdruncsudoapt-getpurgedocker-cedocker-ce-clicontainerd.iosudorm-rf/var/lib/dockersudorm-rf/etc/dockersudorm-rf/var/run/docker.socksudo......
  • 【LeetCode】2288. 价格减免
    一、题目描述句子是由若干个单词组成的字符串,单词之间用单个空格分隔,其中每个单词可以包含数字、小写字母、和美元符号'$'。如果单词的形式为美元符号后跟着一个非负实数,那么这个单词就表示一个价格。例如"$100"、"$23"和"$6.75"表示价格,而"100"、"$"和"2$3"不是。注意:......
  • 国标GB28181智能视频监控LiteCVR设备列表显示不全的原因排查
    随着科技的不断发展,安防视频监控技术也在不断创新和升级。近年来,一些新技术不断涌现,为安防视频监控领域带来了更多的机遇和挑战。有用户在现场部署LiteCVR,服务器重启后,设备列表显示不全,只显示国标设备,不显示Ehome设备,如下图: GB28181视频监控国标平台/视频云存储/安防监控LiteCVR......