【讀書筆記】.Net並行編程高級教程(二)-- 任務並行

      前面一篇提到例子都是數據並行,但這並非並行化的惟一形式,在.Net4以前,必需要建立多個線程或者線程池來利用多核技術。如今只須要使用新的Task實例就能夠經過更簡單的代碼解決命令式任務並行問題。html

1.Task及它的生命週期編程

      一個Task表示一個異步操做,它的建立和執行都是獨立的,所以能夠對相關操做的執行擁有徹底的控制權;當有不少異步操做做爲Task實例加載的時候,爲了充分利用運行時的邏輯內核,任務調度器會嘗試並行的運行這些任務,固然任務都是有額外的開銷,雖然要小於添加線程的開銷;併發

      對Task實例的生命週期的理解很是重要。一個Task的執行,取決於底層硬件和運行時可用的資源。所以Task實例的狀態會不斷的發生改變,而一個Task實例只會完成其生命週期一次,當Task到達它三種可能的最終狀態只後,它就回不去以前的任何狀態了。異步

      

      Task實例有三種可能的初始狀態,Created是Task構造函數建立實例的初始狀態,WaitForActivation是子任務依賴其餘任務完成後等待調度的初始狀態,WaitingToRun是經過TaskFactory.StartNew所建立任務的初始狀態。表示正在等待調度器挑選本身並運行。ide

      任務開始執行,狀態就變爲TaskStatus.Runing。若是還有子任務,主任務的狀態會轉變到TaskStatus.WaitingForChildrenToComplete狀態。並最終到達,Canceled,Faulted和RunToCompletion 三種狀態。從字面理解就是任務取消,出錯和完成。函數

2.任務並行。學習

     前面咱們經過Parallel.Invoke來並行加載方法。this

  Parallel.Invoke(GenerateAESKeys,GenerateMD5Has);

     經過Task實例也能完成一樣的工做。spa

      var t1 = new Task(GenerateAESKeys);
      var t2 = new Task(GenerateMD5Has);
      t1.Start();
      t2.Start();
      Task.WaitAll(t1, t2);

 Start方法對委託進行初始化。 WaitAll方法會等待兩個任務的執行完成以後再往下走。pwa

能夠看見,執行過程當中,任務的狀態不斷的發生變化。能夠給WaitFor方法加上毫秒數。看任務是否會在指定時間內完成。

  if(!Task.WaitAll(new[]{t1,t2},3000))
            {
                Console.WriteLine("任務執行超過3秒");
                Console.WriteLine(t1.Status.ToString());
                Console.WriteLine(t2.Status.ToString());
 }

即便到達了指定時間,任務仍是繼續執行。

一樣任務自己也是能夠等待

  if (t1.Wait(3000))
   {
    Console.WriteLine("任務t1執行超過3秒");
    Console.WriteLine(t1.Status.ToString());
    }

3.經過取消標記取消任務。

    能夠經過CancellationToken 來中斷任務的執行。這須要再委託中添加一些代碼,建立能夠取消的任務。

  private static void GenerateMD5HasCancel(CancellationToken ct)
        {
            ct.ThrowIfCancellationRequested(); var sw = Stopwatch.StartNew();
            for (int i = 0; i < NUM_AES_KEYS; i++)
            {
                var md5M = MD5.Create();
                byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i);
                byte[] result = md5M.ComputeHash(data);
                string hexString = ConverToHexString(result);
                ct.ThrowIfCancellationRequested();
            }
            Console.WriteLine("MD5:" + sw.Elapsed.ToString());
        }
            Console.WriteLine("任務開始...");
            var cts = new CancellationTokenSource();
            var ct = cts.Token;
            var sw = Stopwatch.StartNew();
            var t1 = Task.Factory.StartNew(() => GenerateMD5HasCancel(ct), ct);
            var t2 = Task.Factory.StartNew(() => GenerateAESKeysCancel(ct), ct);
            
            //1秒後取消任務
            Thread.Sleep(1000);

            cts.Cancel();

            try
            {
                if (!Task.WaitAll(new[] { t1,t2}, 1000))
                {
                    Console.WriteLine("任務執行超過1秒");
                    Console.WriteLine(t1.Status.ToString());
                }
            }
            catch (AggregateException ex)
            {
                foreach (var exc in ex.InnerExceptions)
                {
                    Console.WriteLine(exc.ToString());
                }
                if (t1.IsCanceled)
                {
                    Console.WriteLine("任務1取消了...");
                }
                Console.WriteLine(sw.Elapsed.ToString());
                Console.WriteLine("結束");
            }
CancellationTokenSource可以初始化取消的請求,而CancellationToken能將這些請求傳遞給異步操做;上面的方法經過Task類的Factory方法獲得一個TaskFactory實例,相比Task直接建立任務,這個實例可使用更多的功能。而StartNew 等價於用Task構造函數建立一個Task並調用Start方法執行。

直接在Debug下面運行,程序會在異常的地方中斷。直接運行exe獲得上面的結果。 

ThrowIfCancellationRequested在每一次循環迭代都會執行,內部是判斷任務取消後拋出一個OperationCanceledException的異常,來避免運行沒必要要的循環和其餘命令。

  public void ThrowIfCancellationRequested()
        {
            if (IsCancellationRequested) 
                ThrowOperationCanceledException();
        }
 private void ThrowOperationCanceledException()
        {
            throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
        }

若是有代碼正在等待取消,還會自動拋出一個TaskCanceledException異常。會包含在AggregateException中。

4.處理異常。

  修改上面的方法拋出一個異常。

 private static void GenerateMD5HasCancel(CancellationToken ct)
        {
            ct.ThrowIfCancellationRequested();
             //....if (sw.Elapsed.TotalSeconds > 0.5)
                {
                    throw new TimeoutException("超時異常0.5秒");
                }
                ct.ThrowIfCancellationRequested();
            }
            Console.WriteLine("MD5:" + sw.Elapsed.ToString());
        }

修改Main方法的Catch。

                if (t1.IsFaulted)
                {
                    foreach (var exc in ex.InnerExceptions)
                    {
                        Console.WriteLine(exc.ToString());
                    }
                    Console.WriteLine(t1.Status.ToString());
                }

執行結果:

當出現異常時,任務的狀態就會轉換爲Faulted。並不會影響另一個任務的執行。

5.從任務返回值。

前面的方法都是沒有返回值,獲得任務的返回值須要使用Task<TResult>實例,TResult要替換爲返回的類型。修改AES方法。返回一個指定前綴的List<String>

GenerateMD5HasList:
 private static List<string> GenerateMD5HasList(CancellationToken ct, char prefix)
        {
            ct.ThrowIfCancellationRequested();
            var sw = Stopwatch.StartNew();
            var list = new List<string>();
            for (int i = 0; i < NUM_AES_KEYS; i++)
            {
                var md5M = MD5.Create();
                byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i);
                byte[] result = md5M.ComputeHash(data);
                string hexString = ConverToHexString(result);
                if (hexString[0] == prefix)
                {
                    list.Add(hexString);
                }
                ct.ThrowIfCancellationRequested();
            }
            Console.WriteLine("MD5:" + sw.Elapsed);
            return list;
        }
View Code
  Console.WriteLine("任務開始...");
            var cts = new CancellationTokenSource();
            var ct = cts.Token;
            
            var t1 = Task.Factory.StartNew(() => GenerateMD5HasList(ct,'A'), ct);
            //等待執行完成
            t1.Wait();

            var res = t1.Result;
            for (int i = 0; i < res.Count; i++)
            {
                Console.WriteLine(res[i]);
            }

而這時的StartNew建立的類型是Task<List<String>>.StartNew源碼以下:

  public Task<TResult> StartNew<TResult>(Func<TResult> function)
        {
            StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
            Task currTask = Task.InternalCurrent;
            return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken,
                m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark);
        }

咱們還能夠將任務串聯起來。好比上面的代碼。避免寫太多代碼來檢查前面一個任務是否完成。而ContinueWith這個方法能夠用來串聯多個任務。

            var t1 = Task.Factory.StartNew(() => GenerateMD5HasList(ct,'A'), ct);
            var t2 = t1.ContinueWith((t) =>
            {
                for (int i = 0; i < t.Result.Count; i++)
                {
                    Console.WriteLine(t.Result[i]);
                                    }
            });
            //能夠等待t2執行完成
            t2.Wait();

若是須要設置繼續的條件,就要用到TaskContinuationOptions,它是一個枚舉類型,用來控制另外一個任務執行和調度的可選行爲

 var t2 = t1.ContinueWith((t) => OtherMethod(t), TaskContinuationOptions.NotOnCanceled);

 NotOnCanceled,就是表示上個任務不取消的狀況下執行。例如還有NotOnFaulted.若是上一個任務拋出了異常,那麼就不會執行。這裏就不一一例舉了。

 

 小結:這一章主要是將了基於任務的編程模型,學習了任務的建立、狀態,以及如何取消、捕獲異常和得到返回值,並能串行任務,任務的延續不只能簡化代碼,並且還能幫助調度器對很快就要執行的任務採起正確的操做。下一章學習併發集合。

 

閱讀書籍:《C#並行編程高級教程 連接: 下載鏈: http://pan.baidu.com/s/1bn1BdBx  密碼: fn2d

 C#並行編程高級教程

喜歡看書,也喜歡分享書籍(不限技術書籍)的朋友,誠邀加入書山有路羣q:452450927 。

第三期書山有路,你們正在讀《女人的起源》。 連接: http://pan.baidu.com/s/1ntEhMHz 密碼: 84d8

 在喜歡你的人那裏,去熱愛生活;在不喜歡你的人那裏,去看清世界。

相關文章
相關標籤/搜索