天天看點

[你必須知道的異步程式設計]——基于任務的異步模式(TAP)一、引言二、什麼是TAP——基于任務的異步模式介紹三、如何使用TAP——使用基于任務的異步模式來異步程式設計四、TAP與APM或EAP可以轉換嗎?——與其他異步模式的轉換五、小結

本專題概要

引言

什麼是TAP——基于任務的異步模式介紹

如何使用TAP——使用基于任務的異步模式來異步程式設計

TAP與APM或EAP可以轉換嗎?——與其他異步模式的轉換

小結

  在上兩個專題中我為大家介紹.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面兩種模式進行異步程式設計的時候,大家多多少少肯定會感覺到實作起來比較麻煩, 首先我個人覺得,當使用APM的時候,首先我們要先定義用來包裝回調方法的委托,這樣難免有點繁瑣, 然而使用EAP的時候,我們又需要實作Completed事件和Progress事件,上面兩種實作方式感覺都有點繁瑣,同時微軟也意思到了這點,是以在.NET 4.0中提出了一個新的異步模式——基于任務的異步模式,該模式主要使用System.Threading.Tasks.Task和Task<T>類來完成異步程式設計,相對于前面兩種異步模式來講,TAP使異步程式設計模式更加簡單(因為這裡我們隻需要關注Task這個類的使用),同時TAP也是微軟推薦使用的異步程式設計模式,下面就具體為大家分享下本專題的内容.

目前我還沒有找到在.NET 類庫中實作了基于任務的異步模式的哪個類提供進度報告的功能,下面的将為大家示範這個實作,并且也是這個程式的亮點,同時通過自己實作TAP的異步方法來進一步了解基于任務的異步模式。

看完上面的介紹,我們是不是很迫不及待想知道如何自己實作一個基于任務的異步模式的異步方法的,并且希望隻需要這個方法就可以完成異步操作的取消和進度報告的功能的(因為EAP中需要實作其他的事件和定義事件參數類型,這樣的實作未免過于複雜),下面就基于上專題中實作的程式用基于任務的異步模式來完成下。下面就讓我們實作自己的異步方法(亮點為隻需要一個方法就可以完成進度報告和異步操作取消的功能):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

<code>//  Download File</code>

<code>        </code><code>// CancellationToken 參數指派獲得一個取消請求</code>

<code>        </code><code>// progress參數負責進度報告</code>

<code>        </code><code>private</code> <code>void</code> <code>DownLoadFile(</code><code>string</code> <code>url, CancellationToken ct, IProgress&lt;</code><code>int</code><code>&gt; progress)</code>

<code>        </code><code>{</code>

<code>            </code><code>HttpWebRequest request = </code><code>null</code><code>;</code>

<code>            </code><code>HttpWebResponse response = </code><code>null</code><code>;</code>

<code>            </code><code>Stream responseStream = </code><code>null</code><code>;</code>

<code>            </code><code>int</code> <code>bufferSize = 2048;</code>

<code>            </code><code>byte</code><code>[] bufferBytes = </code><code>new</code> <code>byte</code><code>[bufferSize];</code>

<code>            </code><code>try</code>

<code>            </code><code>{</code>

<code>                </code><code>request = (HttpWebRequest)WebRequest.Create(url);</code>

<code>                </code><code>if</code> <code>(DownloadSize != 0)</code>

<code>                </code><code>{</code>

<code>                    </code><code>request.AddRange(DownloadSize);</code>

<code>                </code><code>}</code>

<code>                </code><code>response = (HttpWebResponse)request.GetResponse();</code>

<code>                </code><code>responseStream = response.GetResponseStream();</code>

<code>                </code><code>int</code> <code>readSize = 0;</code>

<code>                </code><code>while</code> <code>(</code><code>true</code><code>)</code>

<code>                    </code><code>// 收到取消請求則退出異步操作</code>

<code>                    </code><code>if</code> <code>(ct.IsCancellationRequested == </code><code>true</code><code>)</code>

<code>                    </code><code>{</code>

<code>                        </code><code>MessageBox.Show(String.Format(</code><code>"下載下傳暫停,下載下傳的檔案位址為:{0}\n 已經下載下傳的位元組數為: {1}位元組"</code><code>, downloadPath, DownloadSize));</code>

<code>                        </code><code>response.Close();</code>

<code>                        </code><code>filestream.Close();</code>

<code>                        </code><code>sc.Post((state) =&gt;</code>

<code>                        </code><code>{</code>

<code>                            </code><code>this</code><code>.btnStart.Enabled = </code><code>true</code><code>;</code>

<code>                            </code><code>this</code><code>.btnPause.Enabled = </code><code>false</code><code>;</code>

<code>                        </code><code>}, </code><code>null</code><code>);</code>

<code>                        </code><code>// 退出異步操作</code>

<code>                        </code><code>break</code><code>;</code>

<code>                    </code><code>}</code>

<code>                    </code><code>readSize = responseStream.Read(bufferBytes, 0, bufferBytes.Length);</code>

<code>                    </code><code>if</code> <code>(readSize &gt; 0)</code>

<code>                        </code><code>DownloadSize += readSize;</code>

<code>                        </code><code>int</code> <code>percentComplete = (</code><code>int</code><code>)((</code><code>float</code><code>)DownloadSize / (</code><code>float</code><code>)totalSize * 100);</code>

<code>                        </code><code>filestream.Write(bufferBytes, 0, readSize);</code>

<code>                        </code><code>// 報告進度</code>

<code>                        </code><code>progress.Report(percentComplete);</code>

<code>                    </code><code>else</code>

<code>                        </code><code>MessageBox.Show(String.Format(</code><code>"下載下傳已完成,下載下傳的檔案位址為:{0},檔案的總位元組數為: {1}位元組"</code><code>, downloadPath, totalSize));</code>

<code>                            </code><code>this</code><code>.btnStart.Enabled = </code><code>false</code><code>;</code>

<code>                </code><code>}  </code>

<code>            </code><code>}</code>

<code>            </code><code>catch</code> <code>(AggregateException ex)</code>

<code>                </code><code>// 因為調用Cancel方法會抛出OperationCanceledException異常</code>

<code>                </code><code>// 将任何OperationCanceledException對象都視為以處理</code>

<code>                </code><code>ex.Handle(e =&gt; e </code><code>is</code> <code>OperationCanceledException);</code>

<code>        </code><code>}</code>

這樣隻需要上面的一個方法,我們就可以完成上一專題中檔案下載下傳的程式,我們隻需要在下載下傳按鈕的事件處理程式調用該方法和在暫停按鈕的事件處理程式調用CancellationTokenSource.Cancel方法即可,具體代碼為:

<code>// Start DownLoad File</code>

<code>        </code><code>private</code> <code>void</code> <code>btnStart_Click(</code><code>object</code> <code>sender, EventArgs e)</code>

<code>            </code><code>filestream = </code><code>new</code> <code>FileStream(downloadPath, FileMode.OpenOrCreate);</code>

<code>            </code><code>this</code><code>.btnStart.Enabled = </code><code>false</code><code>;</code>

<code>            </code><code>this</code><code>.btnPause.Enabled = </code><code>true</code><code>;</code>

<code>            </code><code>filestream.Seek(DownloadSize, SeekOrigin.Begin);</code>

<code>            </code><code>// 捕捉調用線程的同步上下文派生對象</code>

<code>            </code><code>sc = SynchronizationContext.Current;</code>

<code>            </code><code>cts = </code><code>new</code> <code>CancellationTokenSource();</code>

<code>            </code><code>// 使用指定的操作初始化新的 Task。</code>

<code>            </code><code>task = </code><code>new</code> <code>Task(() =&gt; Actionmethod(cts.Token), cts.Token);</code>

<code>            </code><code>// 啟動 Task,并将它安排到目前的 TaskScheduler 中執行。</code>

<code>            </code><code>task.Start();</code>

<code>            </code><code>//await DownLoadFileAsync(txbUrl.Text.Trim(), cts.Token,new Progress&lt;int&gt;(p =&gt; progressBar1.Value = p));</code>

<code>        </code><code>// 任務中執行的方法</code>

<code>        </code><code>private</code> <code>void</code> <code>Actionmethod(CancellationToken ct)</code>

<code>            </code><code>// 使用同步上文文的Post方法把更新UI的方法讓主線程執行</code>

<code>            </code><code>DownLoadFile(txbUrl.Text.Trim(), ct, </code><code>new</code> <code>Progress&lt;</code><code>int</code><code>&gt;(p =&gt;</code>

<code>                    </code><code>sc.Post(</code><code>new</code> <code>SendOrPostCallback((result)=&gt;progressBar1.Value=(</code><code>int</code><code>)result),p);</code>

<code>                </code><code>}));</code>

<code>        </code><code>// Pause Download</code>

<code>        </code><code>private</code> <code>void</code> <code>btnPause_Click(</code><code>object</code> <code>sender, EventArgs e)</code>

<code>            </code><code>// 發出一個取消請求</code>

<code>            </code><code>cts.Cancel();</code>

下面看看基于任務的異步模式的實作效果如何的,運作結果:

[你必須知道的異步程式設計]——基于任務的異步模式(TAP)一、引言二、什麼是TAP——基于任務的異步模式介紹三、如何使用TAP——使用基于任務的異步模式來異步程式設計四、TAP與APM或EAP可以轉換嗎?——與其他異步模式的轉換五、小結

點選确定按鈕之後,Download按鈕會重新變成可用,此時我們可以繼續點選Download按鈕來下載下傳進行下載下傳,下載下傳完成之後會下載下傳完成彈出框,運作結果如下:

[你必須知道的異步程式設計]——基于任務的異步模式(TAP)一、引言二、什麼是TAP——基于任務的異步模式介紹三、如何使用TAP——使用基于任務的異步模式來異步程式設計四、TAP與APM或EAP可以轉換嗎?——與其他異步模式的轉換五、小結

從上面的程式代碼我們可以清楚的發現——基于任務的異步模式确實比前面的兩種異步模式更加簡單使用,是以,從.NET Framework 4.0開始,微軟推薦使用TAP來實作異步程式設計,這裡就涉及之前用APM或EAP實作的程式如何遷移到用TAP實作的問題的,同時.NET Framwwork對他們之間的轉換了也做了很好的支援。

4.1 将APM轉換為TAP

<code>// 大家可以對比這兩種實作方式</code>

<code>        </code><code>#region 使用APM實作異步請求</code>

<code>        </code><code>private</code> <code>void</code> <code>APMWay()</code>

<code>            </code><code>WebRequest webRq = WebRequest.Create(</code><code>"http://msdn.microsoft.com/zh-CN/"</code><code>);</code>

<code>            </code><code>webRq.BeginGetResponse(result =&gt;</code>

<code>                </code><code>WebResponse webResponse = </code><code>null</code><code>;</code>

<code>                </code><code>try</code>

<code>                    </code><code>webResponse = webRq.EndGetResponse(result);</code>

<code>                    </code><code>Console.WriteLine(</code><code>"請求的内容大小為: "</code> <code>+ webResponse.ContentLength);</code>

<code>                </code><code>catch</code> <code>(WebException ex)</code>

<code>                    </code><code>Console.WriteLine(</code><code>"異常發生,異常資訊為: "</code> <code>+ ex.GetBaseException().Message);</code>

<code>                </code><code>finally</code>

<code>                    </code><code>if</code> <code>(webResponse != </code><code>null</code><code>)</code>

<code>                        </code><code>webResponse.Close();</code>

<code>            </code><code>}, </code><code>null</code><code>);</code>

<code>        </code><code>#endregion</code>

<code>        </code><code>#region 使用FromAsync方法将APM轉換為TAP</code>

<code>        </code><code>private</code> <code>void</code> <code>APMswitchToTAP()</code>

<code>            </code><code>Task.Factory.FromAsync&lt;WebResponse&gt;(webRq.BeginGetResponse, webRq.EndGetResponse, </code><code>null</code><code>, TaskCreationOptions.None).</code>

<code>                </code><code>ContinueWith(t =&gt;</code>

<code>                    </code><code>WebResponse webResponse = </code><code>null</code><code>;</code>

<code>                    </code><code>try</code>

<code>                        </code><code>webResponse = t.Result;</code>

<code>                        </code><code>Console.WriteLine(</code><code>"請求的内容大小為: "</code> <code>+ webResponse.ContentLength);</code>

<code>                    </code><code>catch</code> <code>(AggregateException ex)</code>

<code>                        </code><code>if</code> <code>(ex.GetBaseException() </code><code>is</code> <code>WebException)</code>

<code>                            </code><code>Console.WriteLine(</code><code>"異常發生,異常資訊為: "</code> <code>+ ex.GetBaseException().Message);</code>

<code>                        </code><code>}</code>

<code>                        </code><code>else</code>

<code>                            </code><code>throw</code><code>;</code>

<code>                    </code><code>finally</code>

<code>                        </code><code>if</code> <code>(webResponse != </code><code>null</code><code>)</code>

<code>                            </code><code>webResponse.Close();</code>

<code>                </code><code>});</code>

上面代碼示範了使用APM的原始實作方式以及如何使用FromAsync方法把APM的實作方式轉換為TAP的實作方法,把這兩種方式放在一起,一是可以幫助大家做一個對比,使大家更容易明白APM與TAP的轉換,二是大家也可以通過上面的對比明白TAP與APM的差別。

4.2 将EAP轉化為TAP

處理APM可以更新為用TAP來實作外,對于EAP,我們同樣可以對其轉換為TAP的方式,下面代碼示範了如何将EAP轉換為TAP的實作方式:

<code>#region 将EAP轉換為TAP的實作方式</code>

<code>            </code><code>// webClient類支援基于事件的異步模式(EAP)</code>

<code>            </code><code>WebClient webClient = </code><code>new</code> <code>WebClient();</code>

<code>            </code><code>// 建立TaskCompletionSource和它底層的Task對象</code>

<code>            </code><code>TaskCompletionSource&lt;</code><code>string</code><code>&gt; tcs = </code><code>new</code> <code>TaskCompletionSource&lt;</code><code>string</code><code>&gt;();</code>

<code>            </code><code>// 一個string下載下傳好之後,WebClient對象會應發DownloadStringCompleted事件</code>

<code>            </code><code>webClient.DownloadStringCompleted += (sender, e) =&gt;</code>

<code>                </code><code>// 下面的代碼是在GUI線程上執行的</code>

<code>                </code><code>// 設定Task狀态</code>

<code>                </code><code>if</code> <code>(e.Error != </code><code>null</code><code>)</code>

<code>                    </code><code>// 試圖将基礎Tasks.Task&lt;TResult&gt;轉換為Tasks.TaskStatus.Faulted狀态</code>

<code>                    </code><code>tcs.TrySetException(e.Error);</code>

<code>                </code><code>else</code> <code>if</code> <code>(e.Cancelled)</code>

<code>                    </code><code>// 試圖将基礎Tasks.Task&lt;TResult&gt;轉換為Tasks.TaskStatus.Canceled狀态</code>

<code>                    </code><code>tcs.TrySetCanceled();</code>

<code>                </code><code>else</code>

<code>                    </code><code>// 試圖将基礎Tasks.Task&lt;TResult&gt;轉換為TaskStatus.RanToCompletion狀态。</code>

<code>                    </code><code>tcs.TrySetResult(e.Result);</code>

<code>            </code><code>};</code>

<code>            </code><code>// 當Task完成時繼續下面的Task,顯示Task的狀态</code>

<code>            </code><code>// 為了讓下面的任務在GUI線程上執行,必須标記為TaskContinuationOptions.ExecuteSynchronously</code>

<code>            </code><code>// 如果沒有這個标記,任務代碼會在一個線程池線程上運作</code>

<code>            </code><code>tcs.Task.ContinueWith(t =&gt;</code>

<code>                </code><code>if</code> <code>(t.IsCanceled)</code>

<code>                    </code><code>Console.WriteLine(</code><code>"操作已被取消"</code><code>);</code>

<code>                </code><code>else</code> <code>if</code> <code>(t.IsFaulted)</code>

<code>                    </code><code>Console.WriteLine(</code><code>"異常發生,異常資訊為:"</code> <code>+ t.Exception.GetBaseException().Message);</code>

<code>                    </code><code>Console.WriteLine(String.Format(</code><code>"操作已完成,結果為:{0}"</code><code>, t.Result));</code>

<code>            </code><code>}, TaskContinuationOptions.ExecuteSynchronously);</code>

<code>            </code><code>// 開始異步操作</code>

<code>            </code><code>webClient.DownloadStringAsync(</code><code>new</code> <code>Uri(</code><code>"http://msdn.microsoft.com/zh-CN/"</code><code>));</code>

<code>            </code><code>#endregion</code>

  本專題關于TAP的内容就介紹到這裡了,本專題主要以實作一個檔案下載下傳程式要講述基于任務的異步模式所帶來的簡便,這個也是.NET 4.0中提出TAP的原因所在吧,最後介紹了TAP與APM和EAP模式之間的轉化,通過這部分大家可以清楚知道以前的異步實作如何向新的異步模式的遷移,以及從他們的轉換實作代碼中也可以比較他們之間的不同。然而在.NET 4.5中,微軟對異步程式設計又做了更好的支援——提供了async和await兩個關鍵字,這兩個關鍵字使我們異步程式設計如同步程式設計一樣的簡單,徹底改變了實作異步程式設計所面臨的委托回調,跨線程通路控件等問題,具體這部分内容,我将在下個專題中為大家介紹。

<a href="http://down.51cto.com/data/2362835" target="_blank">附件:http://down.51cto.com/data/2362835</a>

     本文轉自LearningHard 51CTO部落格,原文連結:http://blog.51cto.com/learninghard/1200926,如需轉載請自行聯系原作者

繼續閱讀