您现在的位置是:首页 > 文章详情

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

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


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


Task代理Unwrap避免内联


那么使用该方法可以解决什么问题呢?首先我们来看如下一个简单例子


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


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


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


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


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


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方法即可避免这种情况。


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


如果任务尚未完成或发生错误或被取消,则将其包装在未包装的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源创计划”,欢迎正在阅读的你也加入,一起分享。

原文链接:https://my.oschina.net/u/4598092/blog/4699158
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章