之前做开发时,一直没注意这个东西,做了.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;
}

C#中CancellationToken和CancellationTokenSource用法的更多相关文章

  1. 运行ABP(asp.net core 3.X+Vue)提示'OFFSET' 附近有语法错误。 在 FETCH 语句中选项 NEXT 的用法无效。

    创建ASP.NET Boilerplate,还原数据库和启动客户端 这里就略过,具体参考 ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) ASP.NET Boilerpl ...

  2. Java中的Socket的用法

                                   Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的 ...

  3. ecshop中foreach的详细用法归纳

    ec模版中foreach的常见用法. foreach 语法: 假如后台:$smarty->assign('test',$test); {foreach from=$test item=list ...

  4. matlab中patch函数的用法

    http://blog.sina.com.cn/s/blog_707b64550100z1nz.html matlab中patch函数的用法——emily (2011-11-18 17:20:33) ...

  5. C#中timer类的用法

    C#中timer类的用法 关于C#中timer类  在C#里关于定时器类就有3个   1.定义在System.Windows.Forms里   2.定义在System.Threading.Timer类 ...

  6. C#中dynamic的正确用法

    C#中dynamic的正确用法  http://www.cnblogs.com/qiuweiguo/archive/2011/08/03/2125982.html dynamic是FrameWork4 ...

  7. C++中typename关键字的用法

    我在我的 薛途的博客 上发表了新的文章,欢迎各位批评指正. C++中typename关键字的用法

  8. Guava中Predicate的常见用法

    Guava中Predicate的常见用法 1.  Predicate基本用法 guava提供了许多利用Functions和Predicates来操作Collections的工具,一般在 Iterabl ...

  9. C++中const 的各种用法

    C++中const 关键字的用法 const修饰变量 const 主要用于把一个对象转换成一个常量,例如: ; size = ; // error: assignment of read-only v ...

随机推荐

  1. Java Web 实现Mysql 数据库备份与还原

    前段时间某某删库事故付出的惨重代价告诉我们: 数据备份的必要性是企业数据管理极其重要的一项工作. 1. Mysql备份与还原命令 备份命令: mysqldump -h127.0.0.1 -uroot ...

  2. ganglia -api

    setup 命令: virtualenv ve source ve/bin/activate pip install -r requirements.txt python ganglia/gangli ...

  3. mysql安装 报错解决

    换了新电脑,重新安装了一下mysql,安装过程出现了一些错误,在此记录一下: 参考菜鸟教程:https://www.runoob.com/mysql/mysql-install.html 1.下载my ...

  4. PowerDotNet平台化软件架构设计与实现系列(06):定时任务调度平台

    定时任务是后端系统开发中少不了的一个基本必备技能. 传统的实现定时任务的方式有很多种,比如直接使用操作系统的Timer和TaskSchedule,或者基于Quartz.HangFire.xxl-job ...

  5. mit6.830 - lab1 - 存储模型 - 题解

    1.Intro github : https://github.com/CreatorsStack/CreatorDB lab1实现数据库基本的存储逻辑结构,具体包括:Tuple,TupleDesc, ...

  6. de1ctf_2019_weapon(爆破_IO_2_1_stdout)

    (这是我真正意义上的完完全全自己做的第一道堆题目,虽然花了快三个小时,谨以此篇纪念一下) 题目的例行检查我就不放了,将程序放入ida中 程序的逻辑十分简单,漏洞也非常明显 重点是这个程序没有给我们sh ...

  7. 拆分行(Power Query 之 M 语言)

    数据源: 略 目标: 将指定列拆分为行 操作过程: 选取指定列>[主页](或[转换])>[拆分列]>[按分隔符]/[按字符数]/[按位置]>高级选项>拆分为[行] M公式 ...

  8. 工作簿拆分(Excel代码集团)

    一个工作簿中包括N个工作表,将各个工作表拆分成工作簿. 工作表数量不定,表内内容不限,拆分后保存于当前文件夹内. Sub Sample() Dim MySheetsCount As Long For ...

  9. Eclipse切换不同版本的jdk

    var会在java1.8中报错,安装10版本以上的jdk可以解决问题,但是安装后Eclipse无法正常工作,后来发现是Eclipse没有切换版本,在网上找了好多教程都是切换系统变量,后来我发现可以直接 ...

  10. CF764B Timofey and cubes 题解

    Content 有一个序列 \(a_1,a_2,a_3,...,a_n\),对于 \(i\in[1,n]\),只要 \(i\leqslant n-i+1\),就把闭区间 \([i,n-i+1]\) 内 ...