目录

  • 前言
  • 1. 多线程请求合并数据源
  • 2. 对长时间阻塞调用的异步取消令牌应用
  • 3. CancellationToken 的链式反应
  • 4. CancellationToken 令牌取消的三种方式
  • 结束语
  • 示例代码下载

前言

    取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码、提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用。

1. 多线程请求合并数据源

在一个很常见的业务场景中,比如当请求一个文章详细信息的时候,需要同时加载部分点赞用户和评论内容,这里一共有 3 个任务,如果按照常规的先请求文章信息,然后再执行请求点赞和评论,那么我们需要逐一的按顺序去数据库中执行 3 次查询;但是利用 CancellationToken ,我们可以对这 3 个请求同时执行,然后在所有数据源都请求完成的时候,将这些数据进行合并,然后输出到客户端

1.1 合并请求文章信息

       public static void Test(){Random rand = new Random();CancellationTokenSource cts = new CancellationTokenSource();List<Task<Article>> tasks = new List<Task<Article>>();TaskFactory factory = new TaskFactory(cts.Token);foreach (var t in new string[] { "Article", "Post", "Love" }){Console.WriteLine("开始请求");tasks.Add(factory.StartNew(() =>{var article = new Article { Type = t };if (t == "Article"){article.Data.Add("文章已加载");}else{for (int i = 1; i < 5; i++){Thread.Sleep(rand.Next(1000, 2000));Console.WriteLine("load:{0}", t);article.Data.Add($"{t}_{i}");}}return article;}, cts.Token));}Console.WriteLine("开始合并结果");foreach (var task in tasks){Console.WriteLine();var result = task.Result;foreach (var d in result.Data){Console.WriteLine("{0}:{1}", result.Type, d);}task.Dispose();}cts.Cancel();cts.Dispose();Console.WriteLine("\nIsCancellationRequested:{0}", cts.IsCancellationRequested);}

上面的代码定义了一个 Test() 方法,在方法内部,首先定义了一个 CancellationTokenSource 对象,该退出令牌源内部创建了一个取消令牌属性 Token ;接下来,使用 TaskFacory 任务工厂创建了 3 个并行任务,并把这个任务存入 List<Task> 列表对象中,在任务开始后,马上迭代 tasks 列表,通过同步获取每个任务的执行 Result 结果,在取消令牌没有收到取消通知的时候,任务将正常的执行下去,在所有任务都执行完成后,将 3 个请求结果输出到控制台中,同时销毁任务释放线程资源;最后,执行 cts.Cancel()取消令牌并释放资源,最后一句代码将输出令牌的状态。

1.2 执行程序,输出结果

通过上面的输出接口,可以看出,红色部分是模拟请求,这个请求时多线程进行的,Post 和 Love 交替出现,是因为在程序中通过线程休眠的方式模拟网络阻塞过程,蓝色为合并结果部分,可以看到,虽然“文章信息”已经加载完成,但是因为 Post 和 Love 还在请求中,由于取消令牌未收到退出通知,所以合并结果会等待信号,在所有线程都执行完成后,通过 cts.Cancel() 通知令牌取消,所有事件执行完成,控制台打印结果黄色部分为令牌状态,显示为 True ,令牌已取消。

2. 对长时间阻塞调用的异步取消令牌应用

在某些场景中,我们需要请求外部的第三方资源,比如请求天气预报信息;但是,由于网络等原因,可能会造成长时间的等待以致业务超时退出,这种情况可以使用 CancellationToken 来进行优化,但请求超过指定时长后退出,而不必针对每个 HttpClient 进行单独的超时设置

2.1 获取天气预报

        public async static Task GetToday(){CancellationTokenSource cts = new CancellationTokenSource();cts.CancelAfter(3000);HttpClient client = new HttpClient();var res = await client.GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts.Token);var result = await res.Content.ReadAsStringAsync();Console.WriteLine(result);cts.Dispose();client.Dispose();}

在上面的代码中,首先定义了一个 CancellationTokenSource 对象,然后马上发起了一个 HttpClient 的 GetAsync 请求(注意,这种使用 HttpClient 的方式是不正确的,详见我的博客 HttpClient的演进和避坑 ;在 GetAsync 请求中传入了一个取消令牌,然后立即发起了退出请求 Console.WriteLine(result); 不管 3 秒后请求是否返回,都将取消令牌等待信号,最后输出结果释放资源

  • 注意:如果是因为取消令牌退出引起请求中断,将会抛出任务取消的异常 TaskCanceledException
  • 执行程序输出结果

3. CancellationToken 的链式反应

可以使用创建一组令牌,通过链接各个令牌,使其建立通知关联,当 CancellationToken 链中的某个令牌收到取消通知的时候,由链式中创建出来的 CancellationToken 令牌也将同时取消

3.1 创建链式测试代码

public async static Task Test(){CancellationTokenSource cts1 = new CancellationTokenSource();CancellationTokenSource cts2 = new CancellationTokenSource();var cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);cts1.Token.Register(() =>{Console.WriteLine("cts1 Canceling");});cts2.Token.Register(() =>{Console.WriteLine("cts2 Canceling");});cts2.CancelAfter(1000);cts3.Token.Register(() =>{Console.WriteLine("root Canceling");});var res = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts1.Token);var result = await res.Content.ReadAsStringAsync();Console.WriteLine("cts1:{0}", result);var res2 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts2.Token);var result2 = await res2.Content.ReadAsStringAsync();Console.WriteLine("cts2:{0}", result2);var res3 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts3.Token);var result3 = await res2.Content.ReadAsStringAsync();Console.WriteLine("cts3:{0}", result3);}

上面的代码定义了 3 个 CancellationTokenSource ,分别是 cts1,cts2,cts3,每个 CancellationTokenSource 分别注册了 Register 取消回调委托,然后,使用 HttpClient 发起 3 组网络请求;其中,设置 cts2 在请求开始 1秒 后退出,预期结果为:当 cts2 退出后,由于 cts3 是使用 CreateLinkedTokenSource(cts1.Token, cts2.Token) 创建出来的,所以 cts3 应该也会被取消,实际上,无论 cts1/cts2 哪个令牌取消,cts3 都会被取消

3.2 执行程序,输出结果

从上图可以看到,红色部分输出结果是:首先 cts2 取消,接着产生了链式反应导致 cts3 也跟着取消,蓝色部分为 cts1 的正常请求结果,最后输出了任务退出的异常信息

4. CancellationToken 令牌取消的三种方式

CancellationToken 定义了三种不同的取消方法,分别是 Cancel(),CancelAfter(),Dispose();这三种方式都代表了不同的行为方式

4.1 演示取消动作

        public static void Test(){CancellationTokenSource cts1 = new CancellationTokenSource();cts1.Token.Register(() =>{Console.WriteLine("\ncts1 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);});cts1.Cancel();Console.WriteLine("cts1 State:{0}", cts1.IsCancellationRequested);CancellationTokenSource cts2 = new CancellationTokenSource();cts2.Token.Register(() =>{Console.WriteLine("\ncts2 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);});cts2.CancelAfter(500);System.Threading.Thread.Sleep(1000);Console.WriteLine("cts2 State:{0}", cts2.IsCancellationRequested);CancellationTokenSource cts3 = new CancellationTokenSource();cts3.Token.Register(() =>{Console.WriteLine("\ncts3 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);});cts3.Dispose();Console.WriteLine("\ncts3 State:{0}", cts3.IsCancellationRequested);}

4.2 执行程序,输出结果如下

  上面的代码定义了 3 个 CancellationTokenSource,分别是 cts1/cts2/cts3;分别执行了 3 中不同的取消令牌的方式,并在取消回调委托中输出线程ID,从输出接口中看出,当程序执行 cts1.Cancel() 方法后,取消令牌立即执行了回调委托,并输出线程ID为:1;cts2.CancelAfter(500) 表示 500ms 后取消,为了获得令牌状态,这里使线程休眠了 1000ms,而 cts3 则直接调用了 Dispose() 方法,从输出结果看出,cts1 运行在和 Main 方法在同一个线程上,线程 ID 都为 1,而 cts2 由于使用了延迟取消,导致其在内部新创建了一个线程,其线程 ID 为 4;最后,cts3由于直接调用了 Dispose() 方法,但是其 IsCancellationRequested 的值为 False,表示未取消,而输出结果也表明,没有执行回调委托

结束语

  • 通过本文,我们学习到了如何在不同的应用场景下使用 CancellationToken
  • 掌握了合并请求、中断请求、链式反应 三种使用方式
  • 最后还了解到三种不同的取消令牌方式,知道了各种不同取消方式的区别

示例代码下载

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.ThreadingDemo

【转】1.9 Asp.Net Core 轻松学-多线程之取消令牌(相关推荐

  1. Asp.Net Core 轻松学-多线程之Task快速上手

    Asp.Net Core 轻松学-多线程之Task快速上手 原文:Asp.Net Core 轻松学-多线程之Task快速上手 前言     Task是从 .NET Framework 4 开始引入的一 ...

  2. Asp.Net Core 轻松学-利用日志监视进行服务遥测

    原文:Asp.Net Core 轻松学-利用日志监视进行服务遥测 前言     在 Net Core 2.2 中,官方文档表示,对 EventListener 这个日志监视类的内容进行了扩充,同时赋予 ...

  3. Asp.Net Core 轻松学-玩转配置文件

    目录 前言 另类方式使用 hosting.json 使程序运行于多个端口 结语 前言     在 .NET Core 项目中,配置文件有着举足轻重的地位:与.NetFramework 不同的是,.NE ...

  4. Asp.NET Core 轻松学-项目目录和文件作用介绍

    前言     上一章介绍了 Asp.Net Core 的前世今生,并创建了一个控制台项目编译并运行成功,本章的内容介绍 .NETCore 的各种常用命令.Asp.Net Core MVC 项目文件目录 ...

  5. Asp.Net Core 轻松学-正确使用分布式缓存

    前言     本来昨天应该更新的,但是由于各种原因,抱歉,让追这个系列的朋友久等了.上一篇文章 在.Net Core 使用缓存和配置依赖策略 讲的是如何使用本地缓存,那么本篇文章就来了解一下如何使用分 ...

  6. Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章

    前言 事情的起因是由于一段简单的数据库连接代码引起,这段代码从语法上看,是没有任何问题:但是就是莫名其妙的报错了,这段代码极其简单,就是打开数据库连接,读取一条记录,然后立即更新到数据库中.但是,惨痛 ...

  7. asp.net core轻松入门之MVC中Options读取配置文件

    接上一篇中讲到利用Bind方法读取配置文件 ASP.NET Core轻松入门Bind读取配置文件到C#实例 那么在这篇文章中,我将在上一篇文章的基础上,利用Options方法读取配置文件 首先注册MV ...

  8. ASP.NET Core轻松入门之Middleware管道模型

    Middleware指的是微软的的asp.net core的管道模型.其原理可以用微软官方的下图展示: 原理如上图,随着Request的发起,HttpContext会经历多个管道处理(图中的箭头游走方 ...

  9. 获取所有task_Asp.Net Core 轻松学-多线程之Task快速上手

    目录 前言 1. Task 的使用方法 3. 处理 Task 中的异常 4. 同步上下文 5. Task 的运行方式 6. 有条件的 Task 结束语 示例代码下载 前言     Task是从 .NE ...

最新文章

  1. 支付宝 php rsa算法,:PHP支付宝接口RSA验证
  2. 使用Team Foundation Server进行源代码管理(转)
  3. Java Lambda表达式初探
  4. 【NLP】文本分类综述 (上)
  5. Python中json模块的使用,以及json.loads()和json.dumps()的区别
  6. java 标识符_java标识符的基础知识
  7. ShuffleNet网络学习笔记
  8. 正常查看网页中压缩的js代码
  9. QQ动态表情包如何制作 堪比沙漠骆驼gif
  10. 上|中国股市九大伪元宇宙概念股
  11. android 通知静音_如何使电话静音(但不包括短信和通知)
  12. 使用Ajax进行前后端交互(一)
  13. 富途出海淘金:泡沫翻涌 焦虑不止
  14. Windows 10如何找回显示桌面图标,一键快速回到桌面
  15. 摄魄人心的独白,静静聆听,你会感悟很多
  16. 一文搞定插入排序算法
  17. Html标签分类及总结
  18. pandas的个人笔记输入
  19. IOS学习之关于导航那些事
  20. 【Machine Learning】【Andrew Ng】- notes(Week 2: Computing Parameters Analytically)

热门文章

  1. phoenix Explain Plan 翻译
  2. Java 支付宝支付,退款,单笔转账到支付宝账户(单笔转账到支付宝账户)
  3. Python之模块与包(下)
  4. discuzX 帖子 有的图片没输出 [attach]12323[/attach]的解决办法
  5. 随手小记·080911
  6. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol52]45.描述一些对抗RSA侧信道攻击的防御方法
  7. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第13篇]概述投影点表达的用途的优点
  8. Sudoku Extension UVALive - 4763
  9. 增加数据_咱晋城人口又增加了?最新数据来了
  10. .net千万级数据导出_记一次解决docker下oracle数据库故障事例