C# CancellationToken和CancellationTokenSource的用法详解

目录
  • CancellationToken
    • 通过Register方法注册的服务只会执行一次!
  • CancellationTokenSource
  • 使用场景一
  • 使用场景二
  • 使用场景三

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执行取消动作!  

使用场景一

  当我们创建异步操作时,可以传入一个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来解决这个问题:  

  主需要往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;
        }

以上就是C# CancellationToken和CancellationTokenSource的用法详解的详细内容,更多关于C# CancellationToken和CancellationTokenSource的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#异步编程几点需要注意的地方

    尽量不要编写返回值类型为void的异步方法 在通常情况下,建议大家不要编写那种返回值类型为void的异步方法,因为这样做会破坏该方法的启动者与方法本身之间的约定,这套约定本来可以确保主调方能够捕获到异步方法所发生的异常. 正常的异步方法是通过它返回的Task对象来汇报异常的.如果执行过程中发生了异常,那么Task对象就进入了faulted(故障)状态.主调方在对异步方法所返回的Task对象做await操作时,该对象若已处在faulted状态,系统则会将执行异步方法的过程中所发生的异常抛出,反之,

  • C#异步的世界(下)

    前言 今天说异步的主要是指C#5的async\await异步.在此为了方便的表述,我们称async\await之前的异步为"旧异步",async\await为"新异步". 新异步的使用 只能说新异步的使用太简单(如果仅仅只是说使用) 方法加上async修饰符,然后使用await关键字执行异步方法,即可.对就是如此简单.像使用同步方法逻辑一样使用异步. public async Task<int> Test() { var num1 = await Get

  • c# 使用异步编程的方法

    怎么使用异步,就是用委托进行处理,如果委托对象在调用列表中只有一个方法,它就可以异步执行这个方法.委托类有两个方法,叫做BeginInvoke和EndInvoke,它们是用来异步执行使用. 异步有三种模式 等待模式,在发起了异步方法以及做了一些其它处理之后,原始线程就中断,并且等待异步方法完成之后再继续. 轮询模式,原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它的事情. 回调模式,原始线程一直在执行,无需等待或检查发起的线程是否完成.在发起的线程中的引用方法完成之后,发起的线程

  • c# 编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)

    一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net.NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用法及配置文件,这对于有些小工具.小程序.小网站来说,有点"杀鸡焉俺用牛刀"的感觉,而且如果对这些日志框架不了解,可能输出来的日志性能或效果未毕是与自己所想的,鉴于这几个原因,我自己重复造轮子,编写了一个轻量级的异步写日志的实用工具类(LogAsyncWriter),这个类还是比较简单的,实

  • C#如何使用Task执行异步操作

    为什么要使用 Task 线程是创建并发的底层工具,因此具有一定的局限性. 没有简单的方法可以从联合(Join)线程得到"返回值".因此必须创建一些共享域.当抛出一个异常时,捕捉和处理异常也是麻烦的. 线程完成之后,无法再次启动该线程.相反,只能联合(Join)它(在进程阻塞当前线程). 任务是可组合的--使用延续将它们串联在一起.它们可以使用线程池减少启动延迟,而且它们可以通过TaskCompletionSource使用回调方法,避免多个线程同时等待I/O密集操作. Task 和 Th

  • C#异步方法返回void与Task的区别详解

    C#异步方法返回void和Task的区别 如果异步(async关键字)方法有返回值,返回类型为T时,返回类型必然是 Task<T>. 但是如果没有返回值,异步方法的返回类型有2种,一个是返回 Task, 一个是返回 void: public async Task CountDownAsync(int count) { for (int i = count; i >= 0; i--) { await Task.Delay(1000); } } public async void Count

  • C#异步的世界(上)

    前言 新进阶的程序员可能对async.await用得比较多,却对之前的异步了解甚少.本人就是此类,因此打算回顾学习下异步的进化史. 本文主要是回顾async异步模式之前的异步,下篇文章再来重点分析async异步模式. APM APM 异步编程模型,Asynchronous Programming Model 早在C#1的时候就有了APM.虽然不是很熟悉,但是多少还是见过的.就是那些类是BeginXXX和EndXXX的方法,且BeginXXX返回值是IAsyncResult接口. 在正式写APM示

  • 如何在C#中使用 CancellationToken 处理异步任务

    在 .NET Core 中使用异步编程已经很普遍了, 你在项目中随处可见 async 和 await,它简化了异步操作,允许开发人员,使用同步的方式编写异步代码,你会发现在大部分的异步方法中,都提供了CancellationToken参数,本文主要介绍下 CancellationTokenSource 和 CancellationToken在异步任务中的使用. 手动取消任务 创建一个 CancellationTokenSource,然后调用异步方法时,传入 CancellationToken,它

  • 基于c# Task自己动手写个异步IO函数

    前言 对于服务端,达到高性能.高扩展离不开异步.对于客户端,函数执行时间是1毫秒还是100毫秒差别不大,没必要为这一点点时间煞费苦心.对于异步,好多人还有误解,如: 异步就是多线程:异步就是如何利用好线程池.异步不是这么简单,否则微软没必要在异步上花费这么多心思.本文就介绍异步最新的实现方式:Task,并自己动手写一个异步IO函数.只有了解了异步函数内部实现方式,才能更好的利用它. 对于c#,异步处理经过了多个阶段,但是对于现阶段异步就是Task,微软用Task来抽象异步操作.以后的异步函数,处

  • c# winform异步不卡界面的实现方法

    快速阅读 如何在winform程序中,让界面不再卡死. 关于委托和AsyncCallback的使用. 界面卡死的原因是因为耗时任务的计算占用了主线程,导致主界面没有办法进行其它操作,比如拖动.造成界面卡死的现象.我们只需要把耗时任务放在子线程中执行就可以了. 子线程的计算结果 要更新到界面中,怎么更新呢,因为不能操作主线程 ,所以要用委托来实现 . 我们来看个例子. 场景 界面上一个按钮加一人richbox , 点击以后获得当前所在年份 代码实现 定义一个委托实现子线程更新主线程 public

随机推荐