如何避免Task内联,你肯定异步任务已中止执行?

【导读】目前私下空余时间在研究远程大文件断点续传下载,正在进行时,正当研究Task时,看到有个Unwrap方法基本没怎么用过,这里记录并学习下


说到Task.Run和Task.Factory.StartNew两者区别以及应该推崇使用哪个,我也建议使用Task.Run,这里不作过多介绍,网上资料一大把。web


Task代理Unwrap避免内联promise


那么使用该方法能够解决什么问题呢?首先咱们来看以下一个简单例子微信


Task<Task<int>> call = Task.Factory.StartNew(async () =>
{
     return await DoSomethingAsync();
});


咱们经过Task.Factory.StartNew异步方法,此时获取内联任务结果将经过嵌套Task包裹,那么若是要是多层嵌套呢?那也没问题,咱们能够直接将返回结果经过var声明便可,就不用再写层层嵌套Task。app


这只是其一,事实状况在于对于多层内联Task,咱们如何处理以及取消令牌等过程当中发生的错误。Unwrap方法的做用就在于此,以下:异步


Task<int> call = Task.Factory.StartNew(async () =>
{
    return await DoSomethingAsync();
}).Unwrap();


直接从该方法单词入手,暂且翻译为“摊开或展开”,就像洋葱般一层层剥开,让咱们看到最终想要的东西,咱们也看看源码怎么讲async


public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task)
{
    if (task == null)
    {
        throw new ArgumentNullException(nameof(task));
    }
    
    return
        !task.IsCompletedSuccessfully ? Task.CreateUnwrapPromise<TResult>(task, 
        lookForOce: false) :
        task.Result ??
        Task.FromCanceled<TResult>(new CancellationToken(true));
}


利用Unwrap方法建立一个代理,它将为咱们处理全部复杂逻辑,同时咱们也能够不用转发取消令牌,它能够为咱们排查全部不一样级别的错误,换言之,它所返回的Task包含全部令牌取消请求和异常处理(依托代理与内联Task协调工做)就像咱们执行一个简单的任务同样,而不用像任务中的任务而进行嵌套编辑器



var call1 = Task.Run(async () =>
{
    return await DoSomethingAsync();
});


Task.Run和Task.Factory.StartNew区别也在于此,由于利用Task.Run直接将返回最终的实际结果,因此对于内联Task,利用Task.Run来的更加实际,而利用Task.Factory.StartNew若没调用Unwrap可能会出现结果不符合咱们所预期学习


一样,利用Task.Factory.StartNew进行追加任务(ContinueWith)时,也会返回一个任务内联另一个任务,经过使用Unwrap方法便可避免这种状况。ui


上述只是我我的对使用Unwrap方法的归纳和总结,这里给出源码中注释解释翻译:this


若是任务还没有完成或发生错误或被取消,则将其包装在未包装的promise中。若是成功完成,那么直接返回其内部任务以免没必要要的包装。若是内部Task为空,返回已取消的任务以匹配与CreateUnwrapPromise相同的语义。


到了这里,想必咱们已经清楚Unwrap方法的做用,咱们来作个练习:若启动一个长时间执行的异步任务,那么如何中止这个异步任务?

public class UnwrapPractice
{
    CancellationTokenSource cancellationToken;

    public void Start()
    {
        cancellationToken = new CancellationTokenSource();

       Task.Factory.StartNew(
            function: ExecuteAsync,
            cancellationToken: cancellationToken.Token,
            creationOptions: TaskCreationOptions.LongRunning,
            scheduler: TaskScheduler.Default);
    }

    public void Stop()
    {
        cancellationToken.Cancel();
    }

    async Task ExecuteAsync()
    {
        Console.WriteLine("进入执行操做");

        while (!cancellationToken.IsCancellationRequested)
        {
            //模拟长时间执行操做
            await Task.Delay(30000);
        }

        Console.WriteLine("退出执行操做");
    }
}

经过上述简单代码实现做业练习,接下来咱们在控制台调用启动和中止,看看结果是否符合预期


static async Task Main(string[] args)
{
    var unwrapPractice = new UnwrapPractice();

    Console.WriteLine("启动");
    unwrapPractice.Start();

    Thread.Sleep(3000);

    Console.WriteLine("中止");
    unwrapPractice.Stop();

    Console.Read();
}



反观上述动态图,此时咱们将发现长时间异步任务经过调用取消令牌根本没有中止(在控制台并无打印出【退出执行操做】字眼),此时咱们可利用Unwrap方法建立此任务代理,而后在调用令牌取消方法后,再执行代理的等待方法,等待取消任务直至完成便可,貌似有点Thread中Join方法的意味,以下:

  public class UnwrapPractice
  {
      Task task;

      CancellationTokenSource cancellationToken;

      public void Start()
      {
          cancellationToken = new CancellationTokenSource();

          Task<Task> _task = Task.Factory.StartNew(
                 function: ExecuteAsync,
                 cancellationToken: cancellationToken.Token,
                 creationOptions: TaskCreationOptions.LongRunning,
                 scheduler: TaskScheduler.Default);

          //获取此任务代理
          task = _task.Unwrap();
      }

      public void Stop()
      {
          cancellationToken.Cancel();

          //等待取消任务完成(重要)
          task.Wait();
      }

      async Task ExecuteAsync()
      {
          Console.WriteLine("进入执行操做");

          while (!cancellationToken.IsCancellationRequested)
          {
              //模拟长时间执行操做
              await Task.Delay(50000);
          }

          Console.WriteLine("退出执行操做");
      }
  }


💡 因而可知,对于长时间异步任务的执行,当咱们想让其中止时,咱们想固然的认为只需调用取消令牌的Cancel方法就可万事大吉,经练习实践证实其实依然在执行,并无彻底中止!


💡 若利用Task.Run直接调用Wait方法便可,咱们也发现,当咱们须要作更多处理,使用Task.Factory.StartNew时,若对各类选项配置没有一个很清楚的认识和理解,很容易用出毛病!

本文分享自微信公众号 - JeffckyShare(JeffckyShare)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索