当前位置:首页 > C#编程 > C#/.net框架 > 正文内容

C#中CancellationToken和CancellationTokenSource用法

Jorge2年前 (2022-05-09)C#/.net框架689

 CancellationToken

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

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

属性如下:  

C#
  //静态属性,获取一个空的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; }

常用方法:  

C#
   //往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:  

C#
 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是已取消状态,这个时候注册的回调都会立即执行: 

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

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

C#
 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绑定: 

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

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

  CancellationTokenSource的取消有4个方法:  

C#
 //立刻取消
    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 表示立即抛出当前发生的异常,后续的回调将会取消执行  

C#
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异常抛出来!  

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

C#
 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为取消状态将异步操作取消执行:

C#
  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方法传入事件委托,或者采用一些诸如模板设计这样的模型去实现:   

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

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

或者  

C#
 class Demo
    {        public event Action Callback;        public void Close()
        {            //关闭
            Thread.Sleep(3000);

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

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

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

C#
    class Demo
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();        public CancellationToken Token { get => cancellationTokenSource.Token; }        public void Close()
        {            //关闭
            Thread.Sleep(3000);

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

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

 

  使用场景三

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

C#
   public CancellationTokenSource Listen()
        {
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();            //循环调度执行
            Task.Run(() =>
            {                while (true)
                {
                    cancellationTokenSource.Token.ThrowIfCancellationRequested();                    //循环执行一些操作
                    Thread.Sleep(1000);
                    Console.WriteLine("Run");
                }
            });            return cancellationTokenSource;
        }


转载自: https://www.cnblogs.com/shanfeng1000/p/13402152.html

扫描二维码推送至手机访问。

版权声明:本文由7点博客发布,如需转载请注明出处。

本文链接:http://6dot.cn/?id=91

标签: .NET.NET框架
分享给朋友:

“C#中CancellationToken和CancellationTokenSource用法” 的相关文章

C#字符串与享元(Flyweight)模式

C#字符串与享元(Flyweight)模式

注:关注这个话题是因为看到C#的关键字 lock时,其传入引用对象。因为string也是引用对象,所以能否做为lock的参数?对于这个问题,要搞明白C#的字符串的一个特点,它使用类似于享元模式的机制。因此在lock中锁字符串是相当不安全的。下面贴子是对C#字符串与享元模式的深入讨论。写这个文章,主要...

关于C#项目引用的一点经验

关于C#项目引用的一点经验

关于项目引用,有几种:(一)这种是引用系统的程序集(二)下面这种是引用你自己的项目“解决方案”(三)最后一种是浏览本机上的项目的dll。对于工程中有几十个项目的软件来说,虽然使用(二)是很方便。但是会编译速度奇慢,而且随着项目越多越慢。貌似他run之前都会把所有项目都试图更新一下。勇哥宿舍的电脑,实...

c#——表达式树在LINQ动态查询

c#——表达式树在LINQ动态查询

一般如果逻辑比较简单,只是存在有的情况多一个查询条件,有的情况不需要添加该查询条件简单方式这样操作就可以了C#public IQueryable<FileImport> DynamicChainedSyntax (IQueryable<FileImport&g...

C# 查询运算符测试

C# 查询运算符测试

测试一下全部的查询运算符。每天测试一部分,代码会持续更新中……C#using System;using System.Collections;using System.Collections.Generic;using System.Linq;using&nb...

细说进程、应用程序域与上下文之间的关系(三)—— 深入了解.NET上下文

细说进程、应用程序域与上下文之间的关系(三)—— 深入了解.NET上下文

目录一、进程的概念与作用二、应用程序域三、深入了解.NET上下文四、进程应用程序域与线程的关系三、深入了解.NET上下文3.1 .NET上下文的概念应用程序域是进程中承载程序集的逻辑分区,在应用程序域当中,存在更细粒度的用于承载.NET对象的实体,那就.NET上下文Context。所有的.NET对象...

细说进程、应用程序域与上下文之间的关系(一)——进程的概念与作用

细说进程、应用程序域与上下文之间的关系(一)——进程的概念与作用

引言本文主要是介绍进程(Process)、应用程序域(AppDomain)、.NET上下文(Context)的概念与操作。虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提高系统的性能有莫大的帮助。在本篇最后的一节当中将会介绍到三者与线程之间的关系,希望对多线程开发人员能提供...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。