基於任務的編程模型TAP

1、引言

  在上兩個專題中我爲你們介紹.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面兩種模式進行異步編程的時候,你們多多少少確定會感受到實現起來比較麻煩, 首先我我的以爲,當使用APM的時候,首先咱們要先定義用來包裝回調方法的委託,這樣不免有點繁瑣, 然而使用EAP的時候,咱們又須要實現Completed事件和Progress事件,上面兩種實現方式感受都有點繁瑣,同時微軟也意思到了這點,因此在.NET 4.0中提出了一個新的異步模式——基於任務的異步模式,該模式主要使用System.Threading.Tasks.Task和Task<T>類來完成異步編程,相對於前面兩種異步模式來說,TAP使異步編程模式更加簡單(由於這裏咱們只須要關注Task這個類的使用),同時TAP也是微軟推薦使用的異步編程模式,下面就具體爲你們分享下本專題的內容.html

2、什麼是TAP——基於任務的異步模式介紹

基於任務的異步模式(Task-based Asynchronous Pattern,TAP)之因此被微軟所推薦,主要就它使用簡單,基於任務的異步模式使用單個方法來表示異步操做的開始和完成,然而異步編程模型(APM)卻要求BeginXxx和EndXxx兩個方法來分別表示異步操做的開始和完成(這樣使用起來就複雜了),然而,基於事件的異步模式(EAP)要求具備Async後綴的方法和一個或多個事件、事件處理程序和事件參數。看到這裏,是否是你們都有這樣一個疑問的——咱們怎樣區分.NET類庫中的類實現了基於任務的異步模式呢? 這個識別方法很簡單,當看到類中存在TaskAsync爲後綴的方法時就表明該類實現了TAP, 而且基於任務的異步模式一樣也支持異步操做的取消和進度的報告的功能,可是這兩個實現都不像EAP中實現的那麼複雜,由於若是咱們要本身實現EAP的類,咱們須要定義多個事件和事件處理程序的委託類型和事件的參數(具體能夠查看上一專題中的BackgroundWorker剖析部分),可是在TAP實現中,咱們只須要經過向異步方法傳入CancellationToken 參數,由於在異步方法內部會對這個參數的IsCancellationRequested屬性進行監控,當異步方法收到一個取消請求時,異步方法將會退出執行(具體這點可使用反射工具查看WebClient的DownloadDataTaskAsync方法,同時也能夠參考我後面部分本身實現基於任務的異步模式的異步方法。),在TAP中,咱們能夠經過IProgress<T>接口來實現進度報告的功能,具體實現能夠參考我後面的程序部分。web

目前我尚未找到在.NET 類庫中實現了基於任務的異步模式的哪一個類提供進度報告的功能,下面的將爲你們演示這個實現,而且也是這個程序的亮點,同時經過本身實現TAP的異步方法來進一步理解基於任務的異步模式。編程

3、如何使用TAP——使用基於任務的異步模式來異步編程

 看完上面的介紹,咱們是否是很火燒眉毛想知道如何本身實現一個基於任務的異步模式的異步方法的,而且但願只須要這個方法就能夠完成異步操做的取消和進度報告的功能的(由於EAP中須要實現其餘的事件和定義事件參數類型,這樣的實現未免過於複雜),下面就基於上專題中實現的程序用基於任務的異步模式來完成下。下面就讓咱們實現本身的異步方法(亮點爲只須要一個方法就能夠完成進度報告和異步操做取消的功能):異步

        //  Download File
        // CancellationToken 參數賦值得到一個取消請求
        // progress參數負責進度報告
        private void DownLoadFile(string url, CancellationToken ct, IProgress<int> progress)
        {
            HttpWebRequest request = null;
            HttpWebResponse response = null;
            Stream responseStream = null;
            int bufferSize = 2048;
            byte[] bufferBytes = new byte[bufferSize];
            try
            {
                request = (HttpWebRequest)WebRequest.Create(url);
                if (DownloadSize != 0)
                {
                    request.AddRange(DownloadSize);
                }
                response = (HttpWebResponse)request.GetResponse();
                responseStream = response.GetResponseStream();
                int readSize = 0;
                while (true)
                {
                    // 收到取消請求則退出異步操做
                    if (ct.IsCancellationRequested == true)
                    {
                        MessageBox.Show(String.Format("下載暫停,下載的文件地址爲:{0}\n 已經下載的字節數爲: {1}字節", downloadPath, DownloadSize));

                        response.Close();
                        filestream.Close();
                        sc.Post((state) =>
                        {
                            this.btnStart.Enabled = true;
                            this.btnPause.Enabled = false;
                        }, null);
                        // 退出異步操做
                        break;
                    }
                    readSize = responseStream.Read(bufferBytes, 0, bufferBytes.Length);
                    if (readSize > 0)
                    {
                        DownloadSize += readSize;
                        int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);
                        filestream.Write(bufferBytes, 0, readSize);
                        // 報告進度
                        progress.Report(percentComplete);
                    }
                    else
                    {
                        MessageBox.Show(String.Format("下載已完成,下載的文件地址爲:{0},文件的總字節數爲: {1}字節", downloadPath, totalSize));
                        sc.Post((state) =>
                        {
                            this.btnStart.Enabled = false;
                            this.btnPause.Enabled = false;
                        }, null);
                        response.Close();
                        filestream.Close();
                        break;
                    }
                }      
            }
            catch (AggregateException ex)
            {
                // 由於調用Cancel方法會拋出OperationCanceledException異常
                // 將任何OperationCanceledException對象都視爲以處理
                ex.Handle(e => e is OperationCanceledException);
            }
        }
        // Start DownLoad File
        private void btnStart_Click(object sender, EventArgs e)
        {
            filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);
            this.btnStart.Enabled = false;
            this.btnPause.Enabled = true;
            filestream.Seek(DownloadSize, SeekOrigin.Begin);
            // 捕捉調用線程的同步上下文派生對象
            sc = SynchronizationContext.Current;
            cts = new CancellationTokenSource();
            // 使用指定的操做初始化新的 Task。
            task = new Task(() => Actionmethod(cts.Token), cts.Token);
            // 啓動 Task,並將它安排到當前的 TaskScheduler 中執行。 
            task.Start();
            //await DownLoadFileAsync(txbUrl.Text.Trim(), cts.Token,new Progress<int>(p => progressBar1.Value = p));
        }
        // 任務中執行的方法
        private void Actionmethod(CancellationToken ct)
        {
            // 使用同步上文文的Post方法把更新UI的方法讓主線程執行
            DownLoadFile(txbUrl.Text.Trim(), ct, new Progress<int>(p => 
                {
                    sc.Post(new SendOrPostCallback((result)=>progressBar1.Value=(int)result),p);
                }));
        }
        // Pause Download
        private void btnPause_Click(object sender, EventArgs e)
        {
            // 發出一個取消請求
            cts.Cancel();
        }

4、TAP與APM或EAP能夠轉換嗎?——與其餘異步模式的轉換

從上面的程序代碼咱們能夠清楚的發現——基於任務的異步模式確實比前面的兩種異步模式更加簡單使用,因此,從.NET Framework 4.0開始,微軟推薦使用TAP來實現異步編程,這裏就涉及以前用APM或EAP實現的程序如何遷移到用TAP實現的問題的,同時.NET Framwwork對他們之間的轉換了也作了很好的支持。異步編程

4.1 將APM轉換爲TAP 工具

在System.Threading.Tasks命名空間中,有一個TaskFactory(任務工程)類,咱們正能夠利用該類的FromAsync方法來實現將APM轉換爲TAP,下面就用基於任務的異步模式來實如今異步編程模型博文中例子。this

        // 你們能夠對比這兩種實現方式
        #region 使用APM實現異步請求
        private void APMWay()
        {
            WebRequest webRq = WebRequest.Create("http://msdn.microsoft.com/zh-CN/");
            webRq.BeginGetResponse(result =>
            {
                WebResponse webResponse = null;
                try
                {
                    webResponse = webRq.EndGetResponse(result);
                    Console.WriteLine("請求的內容大小爲: " + webResponse.ContentLength);
                }
                catch (WebException ex)
                {
                    Console.WriteLine("異常發生,異常信息爲: " + ex.GetBaseException().Message);
                }
                finally
                {
                    if (webResponse != null)
                    {
                        webResponse.Close();
                    }
                }
            }, null);
        }
        #endregion
        #region 使用FromAsync方法將APM轉換爲TAP
        private void APMswitchToTAP()
        {
            WebRequest webRq = WebRequest.Create("http://msdn.microsoft.com/zh-CN/");
            Task.Factory.FromAsync<WebResponse>(webRq.BeginGetResponse, webRq.EndGetResponse, null, TaskCreationOptions.None).
                ContinueWith(t =>
                {
                    WebResponse webResponse = null;
                    try
                    {
                        webResponse = t.Result;
                        Console.WriteLine("請求的內容大小爲: " + webResponse.ContentLength);
                    }
                    catch (AggregateException ex)
                    {
                        if (ex.GetBaseException() is WebException)
                        {
                            Console.WriteLine("異常發生,異常信息爲: " + ex.GetBaseException().Message);
                        }
                        else
                        {
                            throw;
                        }
                    }
                    finally
                    {
                        if (webResponse != null)
                        {
                            webResponse.Close();
                        }
                    }
                });
        }
        #endregion

上面代碼演示了使用APM的原始實現方式以及如何使用FromAsync方法把APM的實現方式轉換爲TAP的實現方法,把這兩種方式放在一塊兒,一是能夠幫助你們作一個對比,使你們更容易明白APM與TAP的轉換,二是你們也能夠經過上面的對比明白TAP與APM的區別。url

4.2 將EAP轉化爲TAPspa

處理APM能夠升級爲用TAP來實現外,對於EAP,咱們一樣能夠對其轉換爲TAP的方式,下面代碼演示瞭如何將EAP轉換爲TAP的實現方式:線程

#region 將EAP轉換爲TAP的實現方式

            // webClient類支持基於事件的異步模式(EAP)
            WebClient webClient = new WebClient();

            // 建立TaskCompletionSource和它底層的Task對象
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();

            // 一個string下載好以後,WebClient對象會應發DownloadStringCompleted事件
            webClient.DownloadStringCompleted += (sender, e) =>
            {
                // 下面的代碼是在GUI線程上執行的
                // 設置Task狀態
                if (e.Error != null)
                {
                    // 試圖將基礎Tasks.Task<TResult>轉換爲Tasks.TaskStatus.Faulted狀態
                    tcs.TrySetException(e.Error);
                }
                else if (e.Cancelled)
                {
                    // 試圖將基礎Tasks.Task<TResult>轉換爲Tasks.TaskStatus.Canceled狀態
                    tcs.TrySetCanceled();
                }
                else
                {
                    // 試圖將基礎Tasks.Task<TResult>轉換爲TaskStatus.RanToCompletion狀態。
                    tcs.TrySetResult(e.Result);
                }
            };

            // 當Task完成時繼續下面的Task,顯示Task的狀態
            // 爲了讓下面的任務在GUI線程上執行,必須標記爲TaskContinuationOptions.ExecuteSynchronously
            // 若是沒有這個標記,任務代碼會在一個線程池線程上運行
            tcs.Task.ContinueWith(t =>
            {
                if (t.IsCanceled)
                {
                    Console.WriteLine("操做已被取消");
                }
                else if (t.IsFaulted)
                {
                    Console.WriteLine("異常發生,異常信息爲:" + t.Exception.GetBaseException().Message);
                }
                else
                {
                    Console.WriteLine(String.Format("操做已完成,結果爲:{0}", t.Result));
                }
            }, TaskContinuationOptions.ExecuteSynchronously);

            // 開始異步操做
            webClient.DownloadStringAsync(new Uri("http://msdn.microsoft.com/zh-CN/"));
            #endregion
相關文章
相關標籤/搜索