欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-14 10:58:10
取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码、提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,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 执行程序,输出结果

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

通过上面的输出接口,可以看出,红色部分是模拟请求,这个请求时多线程进行的,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
  • 执行程序输出结果

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

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 执行程序,输出结果

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

从上图可以看到,红色部分输出结果是:首先 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 执行程序,输出结果如下

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

  上面的代码定义了 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://files.cnblogs.com/files/viter/ron.threadingdemo.zip