目录
  • 相关类型:
  • cancellationtokensource
  • cancellationtoken
  • 代码示例
    • 思考
  • 关联令牌
    • cancellationchangetoken

      相关类型:

      • 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,最终实现了持续监控

      到此这篇关于.net中异步任务的取消和监控的文章就介绍到这了,更多相关.net中异步任务的取消和监控内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!