服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# CancellationToken和CancellationTokenSource的用法详解

C# CancellationToken和CancellationTokenSource的用法详解

2022-11-23 11:33没有星星的夏季 C#

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

CancellationToken

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

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

   属性如下:  

?
1
2
3
4
5
6
7
8
//静态属性,获取一个空的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; }

  常用方法:  

?
1
2
3
4
5
6
7
//往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:  

?
1
2
3
4
5
6
7
8
9
10
11
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是已取消状态,这个时候注册的回调都会立即执行: 

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

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

?
1
2
3
4
5
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绑定

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

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

  CancellationTokenSource的取消有4个方法:  

?
1
2
3
4
5
6
7
8
//立刻取消
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 表示立即抛出当前发生的异常,后续的回调将会取消执行  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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异常抛出来!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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为取消状态将异步操作取消执行:  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
    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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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方法传入事件委托,或者采用一些诸如模板设计这样的模型去实现: 

?
1
2
3
4
5
6
7
8
9
10
class Demo
{
    public void Close(Action callback)
    {
        //关闭
        Thread.Sleep(3000);
 
        callback?.Invoke();//执行通知
    }
}

  或者  

?
1
2
3
4
5
6
7
8
9
10
11
12
class Demo
{
    public event Action Callback;
 
    public void Close()
    {
        //关闭
        Thread.Sleep(3000);
 
        Callback?.Invoke();//执行通知
    }
}

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

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

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

使用场景三

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        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的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/shanfeng1000/p/13402152.html

延伸 · 阅读

精彩推荐
  • C#C#实现基于ffmpeg加虹软的人脸识别的示例

    C#实现基于ffmpeg加虹软的人脸识别的示例

    本篇文章主要介绍了C#实现基于ffmpeg加虹软的人脸识别的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    sweetwxh8492022-01-24
  • C#如何在C#中使用指针

    如何在C#中使用指针

    这篇文章主要介绍了如何在C#中使用指针,文中代码简单易懂,帮助大家更好的工作和学习,感兴趣的朋友快来了解下...

    一线码农上海6002022-09-08
  • C#C#利用DesignSurface如何实现简单的窗体设计器

    C#利用DesignSurface如何实现简单的窗体设计器

    这篇文章主要介绍了C#利用DesignSurface如何实现简单窗体设计器的相关资料,文中通过图文及示例代码介绍的很详细,对大家具有一定的参考价值,需要的朋...

    JackWang-CUMT4102021-12-27
  • C#C#和Java有什么区别和联系

    C#和Java有什么区别和联系

    这篇文章主要介绍了C#和Java有什么区别和联系的相关资料,本文介绍的非常详细,涉及到rsa语法,c#和java互转方面的知识点,非常不错,具有参考借鉴价值,...

    一只LowCoder8312021-12-01
  • C#C# 实现FTP上传资料的示例

    C# 实现FTP上传资料的示例

    这篇文章主要介绍了C# 实现FTP上传资料的示例,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下...

    農碼一生7932022-10-20
  • C#winform 实现控制输入法

    winform 实现控制输入法

    在工作中遇到这样一个问题,在系统使用过程中,输入法会变灰导致无法使用输入法输入文字,就好像输入法被禁用了没有启用似的。对此,在这里做个备...

    C#教程网9782021-10-21
  • C#RabbitMQ的配置与安装教程全纪录

    RabbitMQ的配置与安装教程全纪录

    这篇文章主要给大家介绍了关于RabbitMQ的配置与安装的相关资料,文中通过示例代码以及图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习...

    小崔的笔记本4312022-02-25
  • C#C#超市收银系统设计

    C#超市收银系统设计

    这篇文章主要为大家详细介绍了C#超市收银系统设计,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    懒懒的5272022-01-10