如何避免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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索