8天玩轉並行開發——第二天 Task的使用


     

     在我們了解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 try
17 {
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 //執行task1
12 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 //執行task1
12 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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM