详细情况:https://www.cnblogs.com/wucy/p/15128365.html

背景

为什么引入取消令牌?

Thread.abort()方法会破坏同步锁中代码的原子逻辑,破坏锁的作用。以下代码说明了Thread.abort()方是如何破坏锁的 :
代码的功能:每个线程进入锁内都会休息10s。

int a = 0;
void RelaxMoment()
{
lock ("")
{ a++;
if (a == 1)
{
Thread.Sleep(10000);
a--;
} }
}

线程A正在锁中sleep中突然被主线程abort停止了,此时a=1。直接导致后续进入锁的线程无法休息。
既然终止一个线程不能使用abort方法,那怎样才能终止一个正在运行的线程呢?答案也很简单,使用自定义的标志位决定线程的执行情况,工作线程内程序员根据标志自行判读该合理的停止的位置,所以就引入标志----取消令牌CancellationToken

CancellationTokenSource原理刨析

CTS是用来发出取消标记,线程和任务根据CTS发出的信号,在自行决定在合适的位置取消线程或任务。

CTS是发出取消标记方式有2种 自动和手动:

1、手动通过调用Cancled()发出取消标记。

2、自动方式通过定时器、指定时间后取消信号。

重要的内部成员

ManualResetEvent? _kernelEvent;  :取消线程暂停

int _state :标记取消

属性

IsCancellationRequested:判断IsCancellationRequested => _state != NotCanceledState;
    Token:封装CTS,返回对CTS操纵源CT,俗称取消令牌

方法

Cancel()

1、将取消标记(_state=1)设置为 "已取消" 状态。   int _state=1;
2 、 TimerQueueTimer? timer.Close(); 取消队列
3、 _kernelEvent?.Set(); //将内部的ManualResetEvent设置为set()
4、ExecuteCallbackHandlers(bool throwOnFirstException);//执行ct.Register(委托)中注册的委托

CreateLinkedTokenSource():将多个CTS链接一起 形成新的CTS,相当于总开关。

CancelAfter(Int32/TimeSpan) :指定过多长时间后取消

Dispose():由于CTS内部有一个内核对象ManualResetEvent实例  所以需要释放,一般GC会自动回收,不用调用该方法。

CancellationToken原理刨析

取消令牌(CT)只是用来封装取消令牌源(CTS)的。线程/任务中通过CT接收CTS源发出的set信号或通过CT不停的侦听CTS状态,判断是否发出取消消息。如果发出取消消息,那么线程/任务在自行决定在合适的位置取消线程或任务。

CT内部封装了一个cts 实例的引用。CTS内部封装了一个 ManualResetEvent 实例对象和int型取消状态 。 线程/任务通过CT内部cts 对象发出的信号,来自行决定在合适的位置取消线程或任务

属性

WaitHandle:返回cts.ManualResetEvent 实例。
None:default
IsCancellationRequested:cts.IsCancellationRequested => cts != null && cts.IsCancellationRequested; 然后可以抛出  ThrowOperationCanceledException();结束线程或任务。

CanBeCanceled:CanBeCanceled => _cts != null;

方法

ThrowIfCancellationRequested()源码:

 public void ThrowIfCancellationRequested()
{
if (IsCancellationRequested)
ThrowOperationCanceledException();
}

 Register():从这个最底层的方法我们可以得知,其本质还是调用CancellationTokenSource的InternalRegister方法,核心操作都不在CancellationToken还是在CancellationTokenSource类,源代码如下:

private CancellationTokenRegistration Register(Delegate callback!!, object? state, bool useSynchronizationContext, bool useExecutionContext)
{
CancellationTokenSource? source = _source;
return source != null ?
source.Register(callback, state, useSynchronizationContext ? SynchronizationContext.Current : null, useExecutionContext ? ExecutionContext.Capture() : null) :
default; // Nothing to do for tokens than can never reach the canceled state. Give back a dummy registration.
}

CancellationTokenSource 取消任务用法详解

CancellationTokenSource.Cancel不代表立即终止代码。它只是通知工作线程 “你可以结束了”。工作线程在关键的代码行中插入监控代码,判断任务是否被取消,这类似于软件工程中的“埋点”,
用户可以在何理的位置抛出异常结束线程。并且在取消任务时候通过CancellationToken.Register处理收尾工作。

if (ct.IsCancellationRequested)
{
Console.WriteLine("Task {0} cancelled", taskNum);
ct.ThrowIfCancellationRequested();
//此方法提供等效于的功能:
//if (token.IsCancellationRequested)
//throw new OperationCanceledException(token);
}

相关的类

CancellationTokenSource 类
CancellationToken 结构
CancellationTokenRegistration 结构
WaitHandle 类
ManualResetEvent 类

取消任务

1、定时取消

创建 CancellationTokenSource 的时候,可以传入时间(毫秒或者Timespan), 通过它我们可以在等待一段时间后,自动取消任务。

CancellationTokenSource cts = new CancellationTokenSource(1000);
_ = Execute(cts.Token);
Console.ReadKey();

我们也可以调用 cts.CancelAfter(1000), 它会在1s后取消任务。

2、第二种方式定时取消任务

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
cancellationToken.Register(() => System.Console.WriteLine("被取消了."));//要在取消 CancellationToken 时执行的委托。
tokenSource.CancelAfter(5000);
while (true)
{
//如果操作被取消则直接抛出异常
cancellationToken.ThrowIfCancellationRequested();
System.Console.WriteLine("一直在执行...");
await Task.Delay(1000);
}
/*
*输出
一直在执行...
一直在执行...
一直在执行...
一直在执行...
一直在执行...
被取消了.
*/

3、具有等待句柄 取消任务

 int eventThatSignaledIndex =WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },new TimeSpan(0, 0, 20));
if (eventThatSignaledIndex == 1)
{
Console.WriteLine("The wait operation was canceled.");
throw new OperationCanceledException(token);
//其他代码

4、关联取消

将cts1、cts2、cts3多个取消令牌源关联在一起ctsLink,形成新的取消令牌源。 只要cts1、cts2、cts3其中一个令牌发出取消命令,那么ctsLink取消。

//声明几个CancellationTokenSource
CancellationTokenSource cts1 = new ();
CancellationTokenSource cts2 = new ();
CancellationTokenSource cts3 = new(); cts2.Token.Register(() => System.Console.WriteLine("tokenSource2被取消了"));
cts1.Token.Register(() => System.Console.WriteLine("tokenSource1被取消了"));
//创建一个关联的CancellationTokenSource
var ctsLink = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token, cts3.Token);
ctsLink.Token.Register(() => System.Console.WriteLine("ctsLink被取消了"));
//取消tokenSource2
cts3.Cancel();
//输出 ctsLink被取消了

5、不允许被取消的操作

要执行一个不允许被取消的操作,可向该操作传递通过调用CancellationToken的静态None属性而返回的CancellationToken。
 该属性返回一个特殊的CancellationToken实例,它不和任何CancellationTokenSource对象关联(实例的私有字段为null)。
由于没有CancellationTokenSource,所以没有代码能调用Cancel .一个操作如果查询这个特殊CancellationToken的 IsCancellationRequested属性,将总是返回false.使用某个特殊 CancellationToken实例查询CancellationToken的CanBeCanceled属性,属性会返回false。
相反,对于通过查询CancellationTokenSource对象的 Token属性而获得的其他所有CancellationToken实例,该属性(CanBeCanceled)都会返回 true.

CancellationToken 注册回调

我们可以调用 Register()方法,注册Token取消的回调,参数需要传入 Action 委托。

CancellationTokenSource cts = new CancellationTokenSource(1000);
cts.Token.Register(() => Console.WriteLine("任务已取消!"));
// 开始异步任务
_ = Execute(cts.Token);
Console.ReadKey();

Register() 注册回调后,返回一个 CancellationTokenRegistration 对象,同样的,你可以在回调函数执行前,移除注册回调,就像这样:

CancellationTokenSource tokenSource = new  ();
CancellationTokenRegistration cancellationTokenRegistration = tokenSource.Token.Register(() => Console.WriteLine(""));
cancellationTokenRegistration.Unregister();
tokenSource.Cancel();
 

C#CancellationToken/CancellationTokenSource-取消令牌/取消令牌源 CT/CTS的更多相关文章

  1. 第七节:利用CancellationTokenSource实现任务取消和利用CancellationToken类检测取消异常。

    一. 传统的线程取消 所谓的线程取消,就是线程正在执行的过程中取消线程任务. 传统的线程取消,是通过一个变量来控制,但是这种方式,在release模式下,被优化从cpu高速缓存中读取,而不是从内存中读 ...

  2. 创建CancellationTokenSource对象用于取消Task

    虽然使用线程池ThreadPool让我们使用多线程变得容易,但是因为是由系统来分配的,如果想对线程做精细的控制就不太容易了,比如某个线程结束后执行一个回调方法.恰好Task可以实现这样的需求.这篇文章 ...

  3. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_10-SpringSecurityOauth2研究-校验令牌&刷新令牌

    3.5校验令牌 Spring Security Oauth2提供校验令牌的端点,如下: Get: http://localhost:40400/auth/oauth/check_token?token ...

  4. 浅谈C#取消令牌CancellationTokenSource

    前言 相信大家在使用C#进行开发的时候,特别是使用异步的场景,多多少少会接触到CancellationTokenSource.看名字就知道它和取消异步任务相关的,而且一看便知大名鼎鼎的Cancella ...

  5. 多线程笔记-CancellationToken(取消令牌)

    介绍     为什么需要CancellationToken?因为Task没有方法支持在外部取消Task,只能通过一个公共变量存放线程的取消状态,在线程内部通过变量判断线程是否被取消,当Cancella ...

  6. Asp.Net Core 轻松学-多线程之取消令牌

    前言     取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码.提升服务性能的效果 ...

  7. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  8. 多线程之旅(9)_如何安全的取消正在执行的线程——附C#源码

    参考网址: https://blog.csdn.net/yangwohenmai1/article/details/90404497 当线程能流畅安全的自动运行后,我们就要考虑一些更风骚的操作,就是如 ...

  9. 浅谈C#更改令牌ChangeToken

    前言 在上篇文章浅谈C#取消令牌CancellationTokenSource一文中我们讲解了CancellationTokenSource,它的主要功能就是分发一个令牌,当我取消令牌我可以进行一些回 ...

随机推荐

  1. Redis内存满了怎么办(新年快乐)

    Redis内存满了怎么办(新年快乐) 入我相思门,知我相思苦. 长相思兮长相忆,短相思兮无穷极. 一.配置文件 Redis长期使用或者不设置过期时间,导致内存爆满或不足,可以到Redis的配置文件re ...

  2. 最大公因数与最小公倍数-gcd&lcm

    一.一些性质 \(gcd(a,b)=gcd(b,a)\) \(gcd(-a,b)=gcd(a,b)\) \(gcd(a,a)=|a|, gcd(a,0)=|a|\) \(gcd(a,1)=1\) \( ...

  3. CF1574F Occurrences

    考虑什么样的串是合法的. 直接考虑比较抽象,考虑具象化这个问题. 容易发现一个字符串的限制就相当于如果出现了其中一个字符 \(a_i = c\),那么 \(s\) 中 \(c\) 前 \(i - 1\ ...

  4. (DDS)正弦波形发生器——幅值、频率、相位可调(一)

    (DDS)正弦波形发生器--幅值.频率.相位可调 一.项目任务: 设计一个幅值.频率.相位均可调的正弦波发生器. 频率每次增加1kHz. 相位每次增加 2*PI/256 幅值每次增加两倍 二.文章内容 ...

  5. String存放位置

    简介 字符串在不同的JDK版本中,存放的位置不同,创建方式不同,存放的位置也不同. 存放位置 JDK1.7以下,无论何种方法创建String对象,位置都位于方法区. JDK1.8及1.8以上,new ...

  6. 浮动float、浮动影响和清除浮动

    普通流(normal flow) 这个单词很多人翻译为 文档流 , 字面翻译 普通流 或者标准流都可以. 前面我们说过,网页布局的核心,就是用CSS来摆放盒子位置.如何把盒子摆放到合适的位置? CSS ...

  7. 解决Wordpress提示FTP登录问题

    向wordpress目录的wordpress-config.php中添加 define("FS_METHOD", "direct"); define(" ...

  8. Token+Redis实现接口幂等性

    一.什么是 幂等性 在编程中,幂等性的特点就是其任意多次执行的效果和一次执行的效果所产生的影响是一样的. 二.Token+Redis的实现思路 1.数据提交前要向服务的申请 token(用户登录时可以 ...

  9. java实现ajax excel导入

    1,jsp js controller FileUpload.fileUp ObjectExcelRead.readExcel

  10. 大前端JS篇之搞懂【Set】

    我认为前端生态很大,犹如一片汪洋大海,很难短时间内窥其全貌,在这里我们不谈其他,只聊聊 Set Set是 es6 新提出的一个对象,也是一种数据结构,为什么es6要提出这样一个新对象呢,无非就是丰富j ...