上集回顧html
上集討論了TPL的線程安全問題,以及很粗淺的討論了一些關於TPL的性能問題。這一集中,暫時拋開這些,直接來討論一下TPL帶來的一個很是強大的新功能——異步撤銷。c#
應用場景安全
還記得線程池吧,利用線程池,按順序每秒輸出一個0-9數字:閉包
1: ThreadPool.QueueUserWorkItem(_ =>
2: {
3: for (int i = 0; i < 10; i++)
4: {
5: Console.WriteLine(i);
6: Thread.Sleep(1000);
7: }
8: });
可是,若是還要有取消功能哪?併發
爲了取消,咱們不得不把代碼寫成這樣:異步
1: bool isCancelled = false;
2: ThreadPool.QueueUserWorkItem(_ =>
3: {
4: for (int i = 0; i < 10; i++)
5: {
6: Console.WriteLine(i);
7: if (isCancelled)
8: break;
9: Thread.Sleep(1000);
10: }
11: });
認爲很好嗎?不,一點也很差,isCancelled會被多個線程訪問,因此,爲了保證CLR不會優化isCancelled,因此須要額外添加volatile關鍵字,給CLR一個Hint。ide
新版的撤銷性能
在4.0中,一樣的操做能夠被這樣實現:學習
1: CancellationTokenSource cts = new CancellationTokenSource();
2: ThreadPool.QueueUserWorkItem(obj =>
3: {
4: CancellationToken token = (CancellationToken)obj;
5: for (int i = 0; i < 10; i++)
6: {
7: Console.WriteLine(i);
8: if (token.IsCancellationRequested)
9: break;
10: Thread.Sleep(1000);
11: }
12: }, cts.Token);
13: cts.Cancel();
看起來沒什麼不一樣,只不過是把一個bool,修改爲了一個類而已。優化
至少如今看起來是這樣,當時若是說撤銷工做沒有這麼簡單的,須要一個Rollback的動做,又會變成怎麼樣哪?
此時,就會發現原來的方式很是難以搞定(至少讓人犯錯誤),而使用新的撤銷機制,能夠寫成這樣:
1: CancellationTokenSource cts = new CancellationTokenSource();
2: ThreadPool.QueueUserWorkItem(obj =>
3: {
4: CancellationToken token = (CancellationToken)obj;
5: for (int i = 0; i < 10; i++)
6: {
7: Console.WriteLine(i);
8: if (token.IsCancellationRequested)
9: {
10: token.Register(() =>
11: {
12: for (int j = i; j >= 0; j--)
13: {
14: Console.WriteLine("撤銷" + j);
15: }
16: });
17: break;
18: }
19: Thread.Sleep(100);
20: }
21: }, cts.Token);
22: Thread.Sleep(500);
23: cts.Cancel();
24: Thread.Sleep(100);
執行結果是:
0
1
2
3
4
5
撤銷4
撤銷3
撤銷2
撤銷1
撤銷0
請按任意鍵繼續. . .
利用CancellationToken.Register方法,能夠在撤消時,額外執行一段撤銷代碼,所以,實現這種撤銷時,實現就變的至關很是的簡單。
ThreadPool尚且能夠這樣玩,那麼是否是該思考一下TPL和這種撤銷的結合哪?
可撤銷的併發任務
先從最簡單的任務開始吧,例如如今有個任務有10個步驟(拋開併發,如今只說順序執行),每一步均可能失敗,而後要求回滾,固然,理想狀態下應該是3步都成功,而後提交,利用Task類,能夠這樣實現一個階段式的任務:
1: using (var cancellation = new CancellationTokenSource())
2: using (var mres = new ManualResetEventSlim(false))
3: {
4: // 添加一個rollbacked任務
5: cancellation.Token.Register(() =>
6: {
7: Console.WriteLine("安裝失敗,而且成功回滾!");
8: mres.Set();
9: });
10: Task[] tasks = new Task[10];
11: // 添加一個Welcome任務
12: var lastTask = Task.Factory.StartNew(() =>
13: {
14: Console.WriteLine("歡迎使用模擬安裝嚮導!");
15: });
16: for (int i = 0; i < 10; i++)
17: {
18: // 知道c#閉包的語法準則的話,必定知道這句話的做用
19: int j = i;
20: tasks[j] = lastTask.ContinueWith(_ =>
21: {
22: // 直接用MessageBox了,偷懶了,呵呵
23: if (MessageBox.Show("是否已經成功執行步驟" + j, "Test", MessageBoxButtons.YesNo) == DialogResult.Yes)
24: {
25: Console.WriteLine("執行步驟" + j + "已經成功。");
26: // 爲每次成功執行任務,添加對應的Rollback任務
27: cancellation.Token.Register(() =>
28: {
29: Console.WriteLine("回滾步驟" + j + "。");
30: });
31: }
32: else
33: {
34: cancellation.Cancel();
35: }
36: }, cancellation.Token);
37: lastTask = tasks[j];
38: }
39: // 添加一個completed任務
40: var completedTask = lastTask.ContinueWith(_ =>
41: {
42: Console.WriteLine("安裝成功!");
43: mres.Set();
44: }, cancellation.Token);
45: mres.Wait();
46: }
運行一個看看,所有步驟點Yes的結果以下:
歡迎使用模擬安裝嚮導!
執行步驟0已經成功。
執行步驟1已經成功。
執行步驟2已經成功。
執行步驟3已經成功。
執行步驟4已經成功。
執行步驟5已經成功。
執行步驟6已經成功。
執行步驟7已經成功。
執行步驟8已經成功。
執行步驟9已經成功。
安裝成功!
請按任意鍵繼續. . .
第0-5步點Yes,第6步點No的結果以下:
歡迎使用模擬安裝嚮導!
執行步驟0已經成功。
執行步驟1已經成功。
執行步驟2已經成功。
執行步驟3已經成功。
執行步驟4已經成功。
執行步驟5已經成功。
回滾步驟5。
回滾步驟4。
回滾步驟3。
回滾步驟2。
回滾步驟1。
回滾步驟0。
安裝失敗,而且成功回滾!
請按任意鍵繼續. . .
是否是有點像那麼回事情。可是,用着Task去不去作多任務併發,是否是感受有點浪費?好,那麼把剛纔的10個任務修改爲並行的看看,10個任務並行執行,若是全完成,則算成功,任何一個失敗,就須要將以前的操做回滾。乍看起來有點難,不過,能夠很簡單的把以前的代碼修改一下:
1: using (var cancellation = new CancellationTokenSource())
2: using (var mres = new ManualResetEventSlim(false))
3: {
4: // 添加一個rollbacked任務
5: cancellation.Token.Register(() =>
6: {
7: Console.WriteLine("安裝失敗,而且成功回滾!");
8: mres.Set();
9: });
10: Task[] tasks = new Task[10];
11: // 添加一個Welcome任務
12: var welcomeTask = Task.Factory.StartNew(() =>
13: {
14: Console.WriteLine("歡迎使用模擬安裝嚮導!");
15: });
16: for (int i = 0; i < 10; i++)
17: {
18: // 知道c#閉包的語法準則的話,必定知道這句話的做用
19: int j = i;
20: tasks[j] = welcomeTask.ContinueWith(_ =>
21: {
22: // 直接用MessageBox了,偷懶了,呵呵
23: if (MessageBox.Show("是否已經成功執行步驟" + j, "Test", MessageBoxButtons.YesNo) == DialogResult.Yes)
24: {
25: Console.WriteLine("執行步驟" + j + "已經成功。");
26: // 爲每次成功執行任務,添加對應的Rollback任務
27: cancellation.Token.Register(() =>
28: {
29: Console.WriteLine("回滾步驟" + j + "。");
30: });
31: }
32: else
33: {
34: cancellation.Cancel();
35: }
36: }, cancellation.Token);
37: }
38: // 添加一個congratulation任務
39: var congratulationTask = Task.Factory.ContinueWhenAll(tasks, _ =>
40: {
41: Console.WriteLine("安裝成功!");
42: mres.Set();
43: }, cancellation.Token);
44: mres.Wait();
45: }
看看修改了什麼:
刪除lastTask = tasks[j];,所以tasks中的任務的前置任務都是welcome任務
把原來congratulation任務的前置任務修改成tasks中的全部任務
這樣tasks中的任務,就會自動並行的執行:
選擇部分「是」,即:
最後一個步驟0選否,就能夠得到以下結果:
有沒有發現回滾的步驟和執行成功的步驟的順序有點不同?
沒錯,由於執行這些步驟的時候是併發的,因此在撤銷的時候也是併發的,因此看到的結果是亂序的。能夠和前面一個純順序的執行方式比較一下,順序執行的撤銷是順序的,並行執行的撤銷是並行的,是否是很神奇?
思考
看到這裏,是否還感受少了點什麼?
若是撤銷失敗了哪?
若是任務有子任務哪?
確實,這方面值得思考的問題還有很多,可是,因爲接觸TPL的時間還不是不少,因此這方面還有待進一步學習。
http://www.cnblogs.com/vwxyzh/archive/2010/04/21/1716735.html