zoukankan      html  css  js  c++  java
  • .Net中异步任务的取消和监控

    相关类型:

    CancellationTokenSource 主要用来创建或取消令牌

    CancellationToken 监听令牌状态,注册令牌取消事件

    OperationCanceledException 令牌被取消时抛出的异常,可以由监听者自主决定是否抛出异常



    CancellationTokenSource

    创建令牌:

    CancellationTokenSource cts = new CancellationTokenSource()
    
    CancellationToken token=cts.Token;
    

    取消释放令牌:

    cts.Cancel();
    


    CancellationToken

    监听令牌取消事件:

    token.Register(() => Console.WriteLine("令牌被取消"));
    

    判断令牌是否取消

    //返回一个bool,如果令牌被取消为true
    token.IsCancellationRequested
    
    //如果token被取消则抛出异常,内部实现其实就是判断IsCancellationRequested
    token.ThrowIfCancellationRequested()=>{
    	if(token.IsCancellationRequested){
    		throw new OperationCanceledException();
    	}
    }
    


    代码示例

    下面模拟一个文件下载的任务,在未下载完成后下载任务被取消

     public void Run()
     {
         CancellationTokenSource cts = new CancellationTokenSource();
    
         Task.Run(() =>
                  {
                      //等待两秒后取消,模拟的是用户主动取消下载任务
                      Thread.Sleep(2000);
                      cts.Cancel();
                  });
    
         try
         {
             var size = DownloadFile(cts.Token);
             Console.WriteLine("文件大小:" + size);
         }
         catch (OperationCanceledException)
         {
             Console.WriteLine("下载失败");
         }finally{
             cts.Dispose();
         }
         Thread.Sleep(2000);
     }
    
    
    /// <summary>
    /// 模拟下载文件,下载文件需要五秒
    /// </summary>
    /// <returns></returns>
    public int DownloadFile(CancellationToken token)
    {
        token.Register(() =>
                       {
                           System.Console.WriteLine("监听到取消事件");
                       });
    
        Console.WriteLine("开始下载文件");
        for (int i = 0; i < 5; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine(i.ToString());
            Thread.Sleep(1000);
        }
        Console.WriteLine("文件下载完成");
        return 100;
    }
    

    输出结果:

    开始下载文件
    0
    1
    监听到取消事件
    下载失败
    

    思考

    为什么要将CancellationToken和CancellationTokenSource分为两个类呢,直接一个CancellationToken又可以取消又可以判断状态注册啥的不是更好,更方便?

    其实每种类的设计和实现都可以有很多不同的策略,CTS和CT从这个两个类提供的为数不多的公开方法中就可以看出,CTS用来控制Token的生成和取消等生命周期状态,CT只能用来监听和判断,无法对Token的状态进行改变。

    所以这种设计的目的就是关注点分离。限制了CT的功能,避免Token在传递过程中被不可控的因素取消造成混乱。



    关联令牌

    继续拿上面的示例来说,示例中实现了从外部控制文件下载功能的终止。

    如果要给文件下载功能加一个超时时间的限制,此时可以增加一个控制超时时间的token,将外部传来的token和内部token 关联起来变为一个token

    只需要将DownloadFile()函数做如下改造即可

    public int DownloadFile(CancellationToken externalToken)
            {
                //通过构造函数设置TokenSource一秒之后调用Cancel()函数
                var timeOutToken = new CancellationTokenSource(new TimeSpan(0, 0, 1)).Token;
                using (var linkToken = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeOutToken))
                {
                    Console.WriteLine("开始下载文件");
                    for (int i = 0; i < 5; i++)
                    {
                        linkToken.Token.ThrowIfCancellationRequested();
                        Console.WriteLine(i.ToString());
                        Thread.Sleep(1000);
                    }
                    Console.WriteLine("文件下载完成");
                    return 100;
                }
            }
    

    此时不论是externalToken取消,或是timeOutToken取消,都会触发linkToken的取消事件



    CancellationChangeToken

    CancellationChangeToken主要用来监测目标变化,需配合ChangeToken使用。从功能场景来说,其实ChangeToken的功能和事件似乎差不多,当监控的目标发生了变化,监听者去做一系列的事情。

    但是事件的话,监听者需要知道目标的存在,就是如果A要注册B的事件,A是要依赖B的。

    CancellationChangeToken是基于CancellationToken来实现的,可以做到依赖于Token而不直接依赖被监听的类

    创建CancellationChangeToken:

    new CancellationChangeToken(new CancellationTokenSource().Token)

    监听Token变动

    new CancellationChangeToken(cts.Token).RegisterChangeCallback(obj => Console.WriteLine("token 变动"), null);

    CancellationChangeToken只是把CancellationToken包装了一层。RegisterChangeCallback最终也是监听的CancellationToken的IsCancellationRequested状态。

    所以就有个问题,代码写到这里,并不能实现每次内部变动都触发回调事件。

    因为CT只会Cancel一次,对应的监听也会执行一次。无法实现多次监听

    为了实现变化的持续监听,需要做两个操作

    • 让Token在Cancel之后重新初始化
    • 每次Cancel回调之后重新监听新的Token

    先上代码,下面的代码实现了每次时间变动都会通知展示面板刷新时间的显示

    public void Run()
    {
        var bjDate = new BeijingDate();
        DisplayDate(bjDate.GetChangeToken, bjDate.GetDate);
        Thread.Sleep(50000);
    }
    
    public void DisplayDate(Func<IChangeToken> getChangeToken, Func<DateTime> getDate)
    {
        ChangeToken.OnChange(getChangeToken, () => Console.WriteLine("当前时间:" + getDate()));
    }
    
    public class BeijingDate
    {
        private CancellationTokenSource cts;
        private DateTime date;
        public BeijingDate()
        {
            cts = new CancellationTokenSource();
            var timer = new Timer(TimeChange, null, 0, 1000);
        }
    
        private void TimeChange(object state)
        {
            date = DateTime.Now;
            var old = cts;
            cts = new CancellationTokenSource();
            old.Cancel();
        }
    
        public DateTime GetDate() => date;
        public CancellationChangeToken GetChangeToken()
        {
            return new CancellationChangeToken(cts.Token);
        }
    }
    

    TimeChange()中修改了时间,重置了Token并将旧的Token取消

    DisplayDate中用ChangeToken.OnChange获取对应的Token并监听

    实现了DisplayData函数和BeijingDate这个类的解耦

    ChangeToken.OnChange 这个函数接收两个参数,一个是获取Token的委托,一个是Token取消事件的响应委托。

    每次在处理完Token的取消事件后,他会重新调用第一个委托获取Token,而此时我们已经生成了新的Token,最终实现了持续监控

  • 相关阅读:
    登录注册测试用例
    礼盒抽奖测试点
    jmeter性能测试
    selenium的常用API及案例演示
    jmeter+ant+jenkins接口自动化测试框架
    开源性能测试工具jmeter
    Jmeter响应断言,性能测试,定时器,cookie
    Charles的介绍,配置与使用
    异常捕获以及文件的相关操作
    函数封装及面向对象
  • 原文地址:https://www.cnblogs.com/bluesummer/p/15219701.html
Copyright © 2011-2022 走看看