【多线程笔记】取消令牌(CancellationToken)

介绍

在使用C#异步的场景,多多少少会接触到CancellationTokenSource。它和取消异步任务相关的,CancellationToken就是它生产出来的。

演示

任务取消执行回调

var tokenSource = new CancellationTokenSource();
tokenSource.Token.Register(() => { Console.WriteLine("线程被取消"); });

延时取消

对长时间阻塞调用的异步取消令牌应用,在某些场景中,我们需要请求外部的第三方资源,但是由于网络等原因,可能会造成长时间的等待以致业务超时退出,这种情况可以使用 CancellationToken 来进行优化,但请求超过指定时长后退出,而不必针对每个 HttpClient 进行单独的超时设置

        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);
        }

关联取消

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

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 都会被取消

判断取消

上面我们使用的方式,都是通过回调的方式得知CancellationTokenSource被取消了,没办法通过标识去得知CancellationTokenSource是否可用。微软为我们提供了IsCancellationRequested属性去判断

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = tokenSource.Token;
//打印被取消
cancellationToken.Register(() => System.Console.WriteLine("被取消了."));
//模拟传递的场景
Task.Run(async ()=> {
    while (!cancellationToken.IsCancellationRequested)
    {
        System.Console.WriteLine("一直在执行...");
        await Task.Delay(1000);
    }
});
//5s之后取消
tokenSource.CancelAfter(5000);

还有另一种方式,也可以主动判断任务是否被取消,不过这种方式简单粗暴,直接是抛出了异常。

while (true)
{
    //如果操作被取消则直接抛出异常
    cancellationToken.ThrowIfCancellationRequested();
}
posted @ 2020-04-08 16:54  .Neterr  阅读(13212)  评论(1编辑  收藏  举报