溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

[你必須知道的異步編程]——基于任務(wù)的異步模式(TAP)

發(fā)布時(shí)間:2020-07-03 21:01:47 來源:網(wǎng)絡(luò) 閱讀:2751 作者:LearningHard 欄目:編程語言

本專題概要

  • 引言

  • 什么是TAP——基于任務(wù)的異步模式介紹

  • 如何使用TAP——使用基于任務(wù)的異步模式來異步編程

  • TAP與APM或EAP可以轉(zhuǎn)換嗎?——與其他異步模式的轉(zhuǎn)換

  • 小結(jié)


一、引言

  在上兩個(gè)專題中我為大家介紹.NET 1.0中的APM和.NET 2.0中的EAP,在使用前面兩種模式進(jìn)行異步編程的時(shí)候,大家多多少少肯定會(huì)感覺到實(shí)現(xiàn)起來比較麻煩, 首先我個(gè)人覺得,當(dāng)使用APM的時(shí)候,首先我們要先定義用來包裝回調(diào)方法的委托,這樣難免有點(diǎn)繁瑣, 然而使用EAP的時(shí)候,我們又需要實(shí)現(xiàn)Completed事件和Progress事件,上面兩種實(shí)現(xiàn)方式感覺都有點(diǎn)繁瑣,同時(shí)微軟也意思到了這點(diǎn),所以在.NET 4.0中提出了一個(gè)新的異步模式——基于任務(wù)的異步模式,該模式主要使用System.Threading.Tasks.Task和Task<T>類來完成異步編程,相對(duì)于前面兩種異步模式來講,TAP使異步編程模式更加簡(jiǎn)單(因?yàn)檫@里我們只需要關(guān)注Task這個(gè)類的使用),同時(shí)TAP也是微軟推薦使用的異步編程模式,下面就具體為大家分享下本專題的內(nèi)容.

二、什么是TAP——基于任務(wù)的異步模式介紹

基于任務(wù)的異步模式(Task-based Asynchronous Pattern,TAP)之所以被微軟所推薦,主要就它使用簡(jiǎn)單,基于任務(wù)的異步模式使用單個(gè)方法來表示異步操作的開始和完成,然而異步編程模型(APM)卻要求BeginXxx和EndXxx兩個(gè)方法來分別表示異步操作的開始和完成(這樣使用起來就復(fù)雜了),然而,基于事件的異步模式(EAP)要求具有Async后綴的方法和一個(gè)或多個(gè)事件、事件處理程序和事件參數(shù)??吹竭@里,是不是大家都有這樣一個(gè)疑問的——我們?cè)鯓訁^(qū)分.NET類庫(kù)中的類實(shí)現(xiàn)了基于任務(wù)的異步模式呢? 這個(gè)識(shí)別方法很簡(jiǎn)單,當(dāng)看到類中存在TaskAsync為后綴的方法時(shí)就代表該類實(shí)現(xiàn)了TAP, 并且基于任務(wù)的異步模式同樣也支持異步操作的取消進(jìn)度的報(bào)告的功能,但是這兩個(gè)實(shí)現(xiàn)都不像EAP中實(shí)現(xiàn)的那么復(fù)雜,因?yàn)槿绻覀円约簩?shí)現(xiàn)EAP的類,我們需要定義多個(gè)事件和事件處理程序的委托類型和事件的參數(shù)(具體可以查看上一專題中的BackgroundWorker剖析部分),但是在TAP實(shí)現(xiàn)中,我們只需要通過向異步方法傳入CancellationToken參數(shù),因?yàn)樵诋惒椒椒▋?nèi)部會(huì)對(duì)這個(gè)參數(shù)的IsCancellationRequested屬性進(jìn)行監(jiān)控,當(dāng)異步方法收到一個(gè)取消請(qǐng)求時(shí),異步方法將會(huì)退出執(zhí)行(具體這點(diǎn)可以使用反射工具查看WebClient的DownloadDataTaskAsync方法,同時(shí)也可以參考我后面部分自己實(shí)現(xiàn)基于任務(wù)的異步模式的異步方法。),在TAP中,我們可以通過IProgress<T>接口來實(shí)現(xiàn)進(jìn)度報(bào)告的功能,具體實(shí)現(xiàn)可以參考我后面的程序部分。

目前我還沒有找到在.NET 類庫(kù)中實(shí)現(xiàn)了基于任務(wù)的異步模式的哪個(gè)類提供進(jìn)度報(bào)告的功能,下面的將為大家演示這個(gè)實(shí)現(xiàn),并且也是這個(gè)程序的亮點(diǎn),同時(shí)通過自己實(shí)現(xiàn)TAP的異步方法來進(jìn)一步理解基于任務(wù)的異步模式。

三、如何使用TAP——使用基于任務(wù)的異步模式來異步編程

看完上面的介紹,我們是不是很迫不及待想知道如何自己實(shí)現(xiàn)一個(gè)基于任務(wù)的異步模式的異步方法的,并且希望只需要這個(gè)方法就可以完成異步操作的取消和進(jìn)度報(bào)告的功能的(因?yàn)镋AP中需要實(shí)現(xiàn)其他的事件和定義事件參數(shù)類型,這樣的實(shí)現(xiàn)未免過于復(fù)雜),下面就基于上專題中實(shí)現(xiàn)的程序用基于任務(wù)的異步模式來完成下。下面就讓我們實(shí)現(xiàn)自己的異步方法(亮點(diǎn)為只需要一個(gè)方法就可以完成進(jìn)度報(bào)告和異步操作取消的功能):

//  Download File
        // CancellationToken 參數(shù)賦值獲得一個(gè)取消請(qǐng)求
        // progress參數(shù)負(fù)責(zé)進(jìn)度報(bào)告
        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)
                {
                    // 收到取消請(qǐng)求則退出異步操作
                    if (ct.IsCancellationRequested == true)
                    {
                        MessageBox.Show(String.Format("下載暫停,下載的文件地址為:{0}\n 已經(jīng)下載的字節(jié)數(shù)為: {1}字節(jié)", 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);
                        // 報(bào)告進(jìn)度
                        progress.Report(percentComplete);
                    }
                    else
                    {
                        MessageBox.Show(String.Format("下載已完成,下載的文件地址為:{0},文件的總字節(jié)數(shù)為: {1}字節(jié)", downloadPath, totalSize));
                        sc.Post((state) =>
                        {
                            this.btnStart.Enabled = false;
                            this.btnPause.Enabled = false;
                        }, null);
                        response.Close();
                        filestream.Close();
                        break;
                    }
                }   
            }
            catch (AggregateException ex)
            {
                // 因?yàn)檎{(diào)用Cancel方法會(huì)拋出OperationCanceledException異常
                // 將任何OperationCanceledException對(duì)象都視為以處理
                ex.Handle(e => e is OperationCanceledException);
            }
        }

這樣只需要上面的一個(gè)方法,我們就可以完成上一專題中文件下載的程序,我們只需要在下載按鈕的事件處理程序調(diào)用該方法和在暫停按鈕的事件處理程序調(diào)用CancellationTokenSource.Cancel方法即可,具體代碼為:

// 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);
            // 捕捉調(diào)用線程的同步上下文派生對(duì)象
            sc = SynchronizationContext.Current;
            cts = new CancellationTokenSource();
            // 使用指定的操作初始化新的 Task。
            task = new Task(() => Actionmethod(cts.Token), cts.Token);
            // 啟動(dòng) Task,并將它安排到當(dāng)前的 TaskScheduler 中執(zhí)行。
            task.Start();
            //await DownLoadFileAsync(txbUrl.Text.Trim(), cts.Token,new Progress<int>(p => progressBar1.Value = p));
        }
        // 任務(wù)中執(zhí)行的方法
        private void Actionmethod(CancellationToken ct)
        {
            // 使用同步上文文的Post方法把更新UI的方法讓主線程執(zhí)行
            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)
        {
            // 發(fā)出一個(gè)取消請(qǐng)求
            cts.Cancel();
        }

下面看看基于任務(wù)的異步模式的實(shí)現(xiàn)效果如何的,運(yùn)行結(jié)果:

[你必須知道的異步編程]——基于任務(wù)的異步模式(TAP)

點(diǎn)擊確定按鈕之后,Download按鈕會(huì)重新變成可用,此時(shí)我們可以繼續(xù)點(diǎn)擊Download按鈕來下載進(jìn)行下載,下載完成之后會(huì)下載完成彈出框,運(yùn)行結(jié)果如下:

[你必須知道的異步編程]——基于任務(wù)的異步模式(TAP)

四、TAP與APM或EAP可以轉(zhuǎn)換嗎?——與其他異步模式的轉(zhuǎn)換

從上面的程序代碼我們可以清楚的發(fā)現(xiàn)——基于任務(wù)的異步模式確實(shí)比前面的兩種異步模式更加簡(jiǎn)單使用,所以,從.NET Framework 4.0開始,微軟推薦使用TAP來實(shí)現(xiàn)異步編程,這里就涉及之前用APM或EAP實(shí)現(xiàn)的程序如何遷移到用TAP實(shí)現(xiàn)的問題的,同時(shí).NET Framwwork對(duì)他們之間的轉(zhuǎn)換了也做了很好的支持。

4.1 將APM轉(zhuǎn)換為TAP

System.Threading.Tasks命名空間中,有一個(gè)TaskFactory(任務(wù)工程)類,我們正可以利用該類的FromAsync方法來實(shí)現(xiàn)將APM轉(zhuǎn)換為TAP,下面就用基于任務(wù)的異步模式來實(shí)現(xiàn)在異步編程模型博文中例子。

// 大家可以對(duì)比這兩種實(shí)現(xiàn)方式
        #region 使用APM實(shí)現(xiàn)異步請(qǐng)求
        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("請(qǐng)求的內(nèi)容大小為: " + webResponse.ContentLength);
                }
                catch (WebException ex)
                {
                    Console.WriteLine("異常發(fā)生,異常信息為: " + ex.GetBaseException().Message);
                }
                finally
                {
                    if (webResponse != null)
                    {
                        webResponse.Close();
                    }
                }
            }, null);
        }
        #endregion
        #region 使用FromAsync方法將APM轉(zhuǎn)換為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("請(qǐng)求的內(nèi)容大小為: " + webResponse.ContentLength);
                    }
                    catch (AggregateException ex)
                    {
                        if (ex.GetBaseException() is WebException)
                        {
                            Console.WriteLine("異常發(fā)生,異常信息為: " + ex.GetBaseException().Message);
                        }
                        else
                        {
                            throw;
                        }
                    }
                    finally
                    {
                        if (webResponse != null)
                        {
                            webResponse.Close();
                        }
                    }
                });
        }
        #endregion

上面代碼演示了使用APM的原始實(shí)現(xiàn)方式以及如何使用FromAsync方法把APM的實(shí)現(xiàn)方式轉(zhuǎn)換為TAP的實(shí)現(xiàn)方法,把這兩種方式放在一起,一是可以幫助大家做一個(gè)對(duì)比,使大家更容易明白APM與TAP的轉(zhuǎn)換,二是大家也可以通過上面的對(duì)比明白TAP與APM的區(qū)別。

4.2 將EAP轉(zhuǎn)化為TAP

處理APM可以升級(jí)為用TAP來實(shí)現(xiàn)外,對(duì)于EAP,我們同樣可以對(duì)其轉(zhuǎn)換為TAP的方式,下面代碼演示了如何將EAP轉(zhuǎn)換為TAP的實(shí)現(xiàn)方式:

#region 將EAP轉(zhuǎn)換為TAP的實(shí)現(xiàn)方式
            // webClient類支持基于事件的異步模式(EAP)
            WebClient webClient = new WebClient();
            // 創(chuàng)建TaskCompletionSource和它底層的Task對(duì)象
            TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
            // 一個(gè)string下載好之后,WebClient對(duì)象會(huì)應(yīng)發(fā)DownloadStringCompleted事件
            webClient.DownloadStringCompleted += (sender, e) =>
            {
                // 下面的代碼是在GUI線程上執(zhí)行的
                // 設(shè)置Task狀態(tài)
                if (e.Error != null)
                {
                    // 試圖將基礎(chǔ)Tasks.Task<TResult>轉(zhuǎn)換為Tasks.TaskStatus.Faulted狀態(tài)
                    tcs.TrySetException(e.Error);
                }
                else if (e.Cancelled)
                {
                    // 試圖將基礎(chǔ)Tasks.Task<TResult>轉(zhuǎn)換為Tasks.TaskStatus.Canceled狀態(tài)
                    tcs.TrySetCanceled();
                }
                else
                {
                    // 試圖將基礎(chǔ)Tasks.Task<TResult>轉(zhuǎn)換為TaskStatus.RanToCompletion狀態(tài)。
                    tcs.TrySetResult(e.Result);
                }
            };
            // 當(dāng)Task完成時(shí)繼續(xù)下面的Task,顯示Task的狀態(tài)
            // 為了讓下面的任務(wù)在GUI線程上執(zhí)行,必須標(biāo)記為TaskContinuationOptions.ExecuteSynchronously
            // 如果沒有這個(gè)標(biāo)記,任務(wù)代碼會(huì)在一個(gè)線程池線程上運(yùn)行
            tcs.Task.ContinueWith(t =>
            {
                if (t.IsCanceled)
                {
                    Console.WriteLine("操作已被取消");
                }
                else if (t.IsFaulted)
                {
                    Console.WriteLine("異常發(fā)生,異常信息為:" + t.Exception.GetBaseException().Message);
                }
                else
                {
                    Console.WriteLine(String.Format("操作已完成,結(jié)果為:{0}", t.Result));
                }
            }, TaskContinuationOptions.ExecuteSynchronously);
            // 開始異步操作
            webClient.DownloadStringAsync(new Uri("http://msdn.microsoft.com/zh-CN/"));
            #endregion

五、小結(jié)

  本專題關(guān)于TAP的內(nèi)容就介紹到這里了,本專題主要以實(shí)現(xiàn)一個(gè)文件下載程序要講述基于任務(wù)的異步模式所帶來的簡(jiǎn)便,這個(gè)也是.NET 4.0中提出TAP的原因所在吧,最后介紹了TAP與APM和EAP模式之間的轉(zhuǎn)化,通過這部分大家可以清楚知道以前的異步實(shí)現(xiàn)如何向新的異步模式的遷移,以及從他們的轉(zhuǎn)換實(shí)現(xiàn)代碼中也可以比較他們之間的不同。然而在.NET 4.5中,微軟對(duì)異步編程又做了更好的支持——提供了async和await兩個(gè)關(guān)鍵字,這兩個(gè)關(guān)鍵字使我們異步編程如同步編程一樣的簡(jiǎn)單,徹底改變了實(shí)現(xiàn)異步編程所面臨的委托回調(diào),跨線程訪問控件等問題,具體這部分內(nèi)容,我將在下個(gè)專題中為大家介紹。



附件:http://down.51cto.com/data/2362835
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI