在我們了解Task之前,如果我們要使用多核的功能可能就會自己來開線程,然而這種線程模型在.net 4.0之后被一種稱為基于
“任務的編程模型”所沖擊,因為task會比thread具有更小的性能開銷,不過大家肯定會有疑惑,任務和線程到底有什么區別?
1:任務是架構在線程之上的,也就是說任務最終還是要拋給線程去執行。
2:任務跟線程不是一對一的關系,比如開10個任務并不是說會開10個線程,這一點任務有點類似線程池,但是任務相比線程池有很小
的開銷和精確的控制。
一:Task
1. 最簡單的使用
開啟task有兩種方式:
<1> 實例化Task
1    //第一種方式開啟2         var task1 = new Task(() =>3         {4             Run1();5         }); 
<2>從工廠中創建
1   var task2 = Task.Factory.StartNew(() =>2             {3                 Run2();4             });是的,同樣兩種方法都可以創建,我們肯定會想兩者是不是多多少少有點區別呢?好的,下面我們舉個例子看分曉。

 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 using System.Diagnostics; 5 using System.Collections.Generic; 6  7 class PRogram 8 { 9     static void Main(string[] args)10     {11         //第一種方式開啟12         var task1 = new Task(() =>13         {14             Run1();15         });16 17         //第二種方式開啟18         var task2 = Task.Factory.StartNew(() =>19             {20                 Run2();21             });22 23         Console.WriteLine("調用start之前****************************/n");24 25         //調用start之前的“任務狀態”26         Console.WriteLine("task1的狀態:{0}", task1.Status);27 28         Console.WriteLine("task2的狀態:{0}", task2.Status);29 30         task1.Start();31 32         Console.WriteLine("/n調用start之后****************************");33 34         //調用start之前的“任務狀態”35         Console.WriteLine("/ntask1的狀態:{0}", task1.Status);36 37         Console.WriteLine("task2的狀態:{0}", task2.Status);38 39         //主線程等待任務執行完40         Task.WaitAll(task1, task2);41 42         Console.WriteLine("/n任務執行完后的狀態****************************");43 44         //調用start之前的“任務狀態”45         Console.WriteLine("/ntask1的狀態:{0}", task1.Status);46 47         Console.WriteLine("task2的狀態:{0}", task2.Status);48 49         Console.Read();50     }51 52     static void Run1()53     {54         Thread.Sleep(1000);55         Console.WriteLine("/n我是任務1");56     }57 58     static void Run2()59     {60         Thread.Sleep(2000);61         Console.WriteLine("我是任務2");62     }63 }

①:從圖中可以看出兩種task實例的簡略生命周期。
Created:表示默認初始化任務,但是我們發現“工廠創建的”實例直接跳過。
WaitingToRun: 這種狀態表示等待任務調度器分配線程給任務執行。
RanToCompletion:任務執行完畢。
②:我們發現task的使用跟Thread很相似,就連waitAll的方法使用也一樣,剛才也說了,任務是架構在線程之上,那么我們用VS里面的
      “并行任務”看一看,快捷鍵Ctrl+D,K,或者找到“調試”->”窗口“->”并行任務“,我們在WaitAll方法處插入一個斷點,最終我們發現
      任務確實托管給了線程。

 
2. 取消任務
   我們知道task是并行計算的,比如說主線程在某個時刻由于某種原因要取消某個task的執行,我們能做到嗎? 當然我們可以做到。
在4.0中給我們提供一個“取消標記”叫做CancellationTokenSource.Token,在創建task的時候傳入此參數,就可以將主線程和任務相
關聯,然后在任務中設置“取消信號“叫做ThrowIfCancellationRequested來等待主線程使用Cancel來通知,一旦cancel被調用。task將會
拋出OperationCanceledException來中斷此任務的執行,最后將當前task的Status的IsCanceled屬性設為true。看起來是不是很抽象,
沒關系,上代碼說話。

 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 using System.Diagnostics; 5 class Program 6 { 7     static void Main(string[] args) 8     { 9         var cts = new CancellationTokenSource();10         var ct = cts.Token;11 12         Task task1 = new Task(() => { Run1(ct); }, ct);13 14         Task task2 = new Task(Run2);15 16         try17         {18             task1.Start();19             task2.Start();20 21             Thread.Sleep(1000);22 23             cts.Cancel();24 25             Task.WaitAll(task1, task2);26         }27         catch (AggregateException ex)28         {29             foreach (var e in ex.InnerExceptions)30             {31                 Console.WriteLine("/nhi,我是OperationCanceledException:{0}/n", e.Message);32             }33 34             //task1是否取消35             Console.WriteLine("task1是不是被取消了? {0}", task1.IsCanceled);36             Console.WriteLine("task2是不是被取消了? {0}", task2.IsCanceled);37         }38 39         Console.Read();40     }41 42     static void Run1(CancellationToken ct)43     {44         ct.ThrowIfCancellationRequested();45 46         Console.WriteLine("我是任務1");47 48         Thread.Sleep(2000);49 50         ct.ThrowIfCancellationRequested();51 52         Console.WriteLine("我是任務1的第二部分信息");53     }54 55     static void Run2()56     {57         Console.WriteLine("我是任務2");58     }59 }
從圖中可以看出
①:Run1中的Console.WriteLine(“我是任務1的第二部分信息”); 沒有被執行。
②:Console.WriteLine(“task1是不是被取消了? {0}”, task1.IsCanceled); 狀態為True。
也就告訴我們Run1中途被主線程中斷執行,我們coding的代碼起到效果了。

 
3. 獲取任務的返回值
  我們以前寫線程的時候注冊的方法一般都是void類型,如果主線程要從工作線程中獲取數據一般采用的手段是“委托+事件”的模式,然而
在Task中有兩種方式可以解決。
<1>  現在我們的實例化是采用Task<TResult>的形式,其中TResult就是當前task執行后返回的結果,下面舉得例子是t2任務獲取
         t1的執行結果。

 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 using System.Diagnostics; 5 using System.Collections.Generic; 6  7 class Program 8 { 9     static void Main(string[] args)10     {11         //執行task112         var t1 = Task.Factory.StartNew<List<string>>(() => { return Run1(); });13 14         t1.Wait();15 16         var t2 = Task.Factory.StartNew(() =>17         {18             Console.WriteLine("t1集合中返回的個數:" + string.Join(",", t1.Result));19         });20 21         Console.Read();22     }23 24     static List<string> Run1()25     {26         return new List<string> { "1", "4", "8" };27     }28 }

<2>采用ContinueWith方法,很有意思,現在我們將上面的方法改造一下。

 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 using System.Diagnostics; 5 using System.Collections.Generic; 6  7 class Program 8 { 9     static void Main(string[] args)10     {11         //執行task112         var t1 = Task.Factory.StartNew<List<string>>(() => { return Run1(); });13 14         var t2 = t1.ContinueWith((i) =>15         {16             Console.WriteLine("t1集合中返回的個數:" + string.Join(",", i.Result));17         });18 19         Console.Read();20     }21 22     static List<string> Run1()23     {24         return new List<string> { "1", "4", "8" };25     }26 }

 
4:ContinueWith結合WaitAll來玩一把
    當這兩者結合起來,我們就可以玩一些復雜一點的東西,比如說現在有7個任務,其中t1需要串行,t2-t3可以并行,t4需要串行,t5-t6并行,
t7串行。

好了,我們上一下代碼說話,下面代碼沒有實際意思,純屬演示。

 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 using System.Diagnostics; 5 using System.Collections.Generic; 6 using System.Collections.Concurrent; 7  8 class Program 9 {10     static void Main(string[] args)11     {12         ConcurrentStack<int> stack = new ConcurrentStack<int>();13 14         //t1先串行15         var t1 = Task.Factory.StartNew(() =>16         {17             stack.Push(1);18             stack.Push(2);19         });20 21         //t2,t3并行執行22         var t2 = t1.ContinueWith(t =>23         {24             int result;25 26             stack.TryPop(out result);27         });28 29         //t2,t3并行執行30         var t3 = t1.ContinueWith(t =>31         {32             int result;33 34             stack.TryPop(out result);35         });36 37         //等待t2和t3執行完38         Task.WaitAll(t2, t3);39 40 41         //t4串行執行42         var t4 = Task.Factory.StartNew(() =>43         {44             stack.Push(1);45             stack.Push(2);46         });47 48         //t5,t6并行執行49         var t5 = t4.ContinueWith(t =>50         {51             int result;52 53             stack.TryPop(out result);54         });55 56         //t5,t6并行執行57         var t6 = t4.ContinueWith(t =>58         {59             int result;60 61             //只彈出,不移除62             stack.TryPeek(out result);63         });64 65         //臨界區:等待t5,t6執行完66         Task.WaitAll(t5, t6);67 68         //t7串行執行69         var t7 = Task.Factory.StartNew(() =>70         {71             Console.WriteLine("當前集合元素個數:" + stack.Count);72         });73 74         Console.Read();75     }76 }

 
新聞熱點
疑難解答