4.0中的并行計算和多線程詳解(二)

作者: ☆磊☆  來源: 博客園  發布時間: 2010-09-21 21:45  閱讀: 2105 次  推薦: 0   原文鏈接   [收藏]  

  相關文章:4.0中的并行計算和多線程詳解(一)

  多線程部分

  多線程在4.0中被簡化了很多,僅僅只需要用到System.Threading.Tasks.::.Task類,下面就來詳細介紹下Task類的使用。

  一、簡單使用

  開啟一個線程,執行循環方法,返回結果。開始線程為Start(),等待線程結束為Wait()。

Code
  1.         /// <summary>
  2.         /// Task簡單使用
  3.         /// </summary>
  4.         private void Demo1()
  5.         {
  6.             int i = 0;
  7.             Random r = new Random(DateTime.Now.Second);
  8.             Task t = new Task(() =>
  9.             {
  10.                 for (int v = 0; v < 100; v++)
  11.                     i += r.Next(100);
  12.             });
  13.             t.Start();
  14.             t.Wait();
  15.             Console.WriteLine("這是執行Task1后等待完成:" + i.ToString());
  16.             Console.ReadLine();
  17.         }

  比以前使用Thread方便多了吧。上面的例子是使用外部的變量獲得結果,下面的例子是用Task<T>直接返回結果,當調用Result屬性時,會自動等待線程結束,等同調用了Wait()。代碼如下:

Code
  1.         /// <summary>
  2.         /// Task帶返回值
  3.         /// </summary>
  4.         private void Demo2()
  5.         {
  6.             Random r = new Random(DateTime.Now.Second);
  7.             Task<int> t = new Task<int>(() =>
  8.             {
  9.                 int i = 0;
  10.                 for (int v = 0; v < 100; v++)
  11.                     i += r.Next(100);
  12.                 return i;
  13.             });
  14.             t.Start();
  15.             Console.WriteLine("這是執行Task1獲取返回值:" + t.Result.ToString());
  16.             Console.ReadLine();
  17.         }

  總結1:Task的使用比Thread簡單很多,減少了同步,等待等等問題,唯一的遺憾是不支持Thread的IsBackground。

  結論1:如果不需要使用IsBackground,那么盡情的使用Task吧。

  二、線程執行完畢后調用另一個線程

  也就是兩個線程,有序的執行,這里使用ContinueWith(),t執行完畢后再執行一個task方法,不多說了代碼如下:

Code
  1.         /// <summary>
  2.         /// Task 執行完畢后調用另一個Task
  3.         /// </summary>
  4.         private void Demo3()
  5.         {
  6.             Random r = new Random(DateTime.Now.Second);
  7.             Task<int> t = new Task<int>(() =>
  8.             {
  9.                 int i = 0;
  10.                 for (int v = 0; v < 100; v++)
  11.                     i += r.Next(100);
  12.                 return i;
  13.             });
  14.             t.ContinueWith((Task<int> task) =>
  15.             {
  16.                 Console.WriteLine("這是執行完畢Task1后繼續調用Task2:" + task.Result.ToString());
  17.             });
  18.             t.Start();
  19.             Console.ReadLine();
  20.         }

  也可以直接鏈式的寫下去,代碼如下:

Code
  1.         /// <summary>
  2.         /// Task 執行完畢后調用另一個Task(鏈式寫法)
  3.         /// </summary>
  4.         private void Demo4()
  5.         {
  6.             Random r = new Random(DateTime.Now.Second);
  7.             Task<int> t = new Task<int>(() =>
  8.             {
  9.                 int i = 0;
  10.                 for (int v = 0; v < 100; v++)
  11.                     i += r.Next(100);
  12.                 return i;
  13.             });
  14.             Task t2 = t.ContinueWith((Task<int> task) =>
  15.             {
  16.                 Console.WriteLine(task.Result.ToString());
  17.             });
  18.             t2.ContinueWith(task =>
  19.             {
  20.                 Console.WriteLine("這是執行完畢Task1后繼續調用Task2,Task2后調用Task3。");
  21.             });
  22.             t.Start();
  23.             Console.ReadLine();
  24.         }

  結論2:Task可以便捷的將幾個方法串行執行。

  三、帶有父子關系的線程/多線程并行開啟

  t帶有t1,t2,t3三個子線程,執行t的時候t1,t2,t3可并行處理,t必須等待t1,t2,t3都執行完畢后才能結束。創建子Task時候必須指定參數為AttachedToParent。

Code
  1.         /// <summary>
  2.         /// 帶有父子關系的Task集合,[TaskCreationOptions.AttachedToParent]
  3.         ///
  4.         /// 值                說明
  5.         /// None              默認值,此Task會被排入Local Queue中等待執行,采用LIFO模式。
  6.         /// AttachedToParent  建立的Task必須是外圍的Task的子Task,也是放入Local Queue,採LIFO模式。
  7.         /// LongRunning       建立的Task不受Thread Pool所管理,直接新增一個Thread來執行此Task,無等待、無排程。
  8.         /// PreferFairness    建立的Task直接放入Global Queue中,採FIFO模式。(比上面的優先級低)
  9.         /// </summary>
  10.         private void Demo5()
  11.         {
  12.             Task<int> t = new Task<int>(() =>
  13.             {
  14.                 Task<int> t1 = new Task<int>(() =>
  15.                 {
  16.                     int i = 0;
  17.                     Random r = new Random(DateTime.Now.Second);
  18.                     for (int v = 0; v < 100; v++)
  19.                         i += r.Next(100);
  20.                     return i;
  21.                 }, TaskCreationOptions.AttachedToParent);
  22.                 Task<int> t2 = new Task<int>(() =>
  23.                 {
  24.                     int i = 0;
  25.                     Random r = new Random(DateTime.Now.Second);
  26.                     for (int v = 0; v < 100; v++)
  27.                         i += r.Next(100);
  28.                     return i;
  29.                 }, TaskCreationOptions.AttachedToParent);
  30.                 Task<int> t3 = new Task<int>(() =>
  31.                 {
  32.                     int i = 0;
  33.                     Random r = new Random(DateTime.Now.Second);
  34.                     for (int v = 0; v < 100; v++)
  35.                         i += r.Next(100);
  36.                     return i;
  37.                 }, TaskCreationOptions.AttachedToParent);
  38.                 t1.Start();
  39.                 t2.Start();
  40.                 t3.Start();
  41.                 return t1.Result + t2.Result + t3.Result;
  42.             });
  43.             t.Start();
  44.             t.Wait();
  45.             Console.WriteLine("這是帶有父子關系的Task集合:" + t.Result.ToString());
  46.             Console.ReadLine();
  47.         }

  結論3:多個線程的同時開啟在這里也很方便,也不用擔心同步等問題。

  四、Task的中斷

  這個很復雜,就不多說了,代碼中有比較詳細的介紹。

Code
  1.         /// <summary>
  2.         /// 中途取消Task執行,Token
  3.         ///
  4.         /// 一是正常結束、二是產生例外、三是透過Cancel機制,這三種情況都會反映在Task.Status屬性上
  5.         /// 值                              說明
  6.         /// Created                         Task已經建立,但未呼叫Start。
  7.         /// WaitingForActivation            Task已排入排程,但尚未執行(一般我們建立的Task不會有此狀態,只有ContinueWith所產生的Task才會有此狀態)。
  8.         /// WaitingToRun                    Task已排入排程,等待執行中。
  9.         /// Running                         Task執行中。
  10.         /// WaitingForChildrenToComplete    Task正等待子Task結束。
  11.         /// RanToCompletion                 Task已經正常執行完畢。
  12.         /// Canceled                        Task已被取消。
  13.         /// Faulted                         Task執行中發生未預期例外。
  14.         ///
  15.         /// 除了Status屬性外,Task還提供了另外三個屬性來判定Task狀態。
  16.         /// 屬性            說明
  17.         /// IsCompleted     Task已經正常執行完畢。
  18.         /// IsFaulted       Task執行中法生未預期例外。
  19.         /// IsCanceled      Task已被取消。
  20.         /// </summary>
  21.         private void Demo6()
  22.         {
  23.             CancellationTokenSource cts = new CancellationTokenSource();
  24.             var ctoken = cts.Token;
  25.             Task t1 = new Task(() =>
  26.             {
  27.                 for (int v = 0; v < 10; v++)
  28.                 {
  29.                     if (ctoken.IsCancellationRequested)
  30.                     {
  31.                         //第一種寫法
  32.                         //這個會拋出異常
  33.                         ctoken.ThrowIfCancellationRequested();
  34.                         //另一種寫法
  35.                         //這個不會返回異常,但是獲取不到是否是中斷還是執行完畢。
  36.                         //return;
  37.                     }
  38.                     Thread.Sleep(1000);
  39.                     Console.WriteLine(v);
  40.                 }
  41.             }, ctoken);
  42.             t1.Start();
  43.             Thread.Sleep(2000);
  44.             cts.Cancel();
  45.             try
  46.             {
  47.                 t1.Wait();
  48.             }
  49.             catch
  50.             {
  51.                 if (t1.IsCanceled)
  52.                     Console.WriteLine("cancel");
  53.             }
  54.             Console.ReadLine();
  55.             cts.Dispose();
  56.         }

  結論4:中斷很復雜,但是對一般邏輯來說,是沒有很大必要的。

  五、其他

  這里介紹下另一種寫法Task.Factory,以及ContinueWhenAny和ContinueWhenAll兩個方法。Task.Factory是靜態工廠類,用于對Task提供一些麻煩的支持,這里主要介紹ContinueWhenAny和ContinueWhenAll。

ContinueWhenAll所指定的函式會在傳入的所有Tasks結束時執行,只會執行一次。ContinueWhenAny所指定的函式會在傳入的Tasks中有任何一個結束時執行,且與ContinueWhenAll相同,只會執行一次。好了,還是看代碼:

Code
  1.         /// <summary>
  2.         /// Task.Factory
  3.         /// ContinueWhenAny和ContinueWhenAll
  4.         /// ContinueWhenAll所指定的函式會在傳入的所有Tasks結束時執行,只會執行一次。
  5.         /// ContinueWhenAny所指定的函式會在傳入的Tasks中有任何一個結束時執行,且與ContinueWhenAll相同,只會執行一次。
  6.         /// </summary>
  7.         private void Demo7()
  8.         {
  9.             Task<int> t1 = Task.Factory.StartNew<int>(() =>
  10.             {
  11.                 int total = 0;
  12.                 for (int i = 0; i < 10; i++)
  13.                     total += i;
  14.                 Thread.Sleep(12000);
  15.                 return total;
  16.             });
  17.             Task<int> t2 = Task.Factory.StartNew<int>(() =>
  18.             {
  19.                 int total = 0;
  20.                 for (int i = 10; i < 20; i++)
  21.                     total += i;
  22.                 Thread.Sleep(10000);
  23.                 return total;
  24.             });
  25.             Task tfinal = Task.Factory.ContinueWhenAny<int>(
  26.                          new Task<int>[] { t1, t2 }, (Task<int> task) =>
  27.                          {
  28.                              if (task.Status == TaskStatus.RanToCompletion)
  29.                              {
  30.                                  Console.WriteLine(task.Result);
  31.                              }
  32.                          });
  33.             Console.ReadLine();
  34.         }

  結論5:ContinueWhenAny和ContinueWhenAll對特定條件執行,還是有些用處的。

  好了,這篇文章算是完結了,4.0中的并行和線程確實簡單了很多,使用起來也很方便,為了性能的提升還是要適當的使用下。

0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()