3、.NET 4.0基於任務的異步模式(TAP),推薦使用

1、異步編程模型(APM)html

2、基於事件的異步編程模式(EAP) 前端

3、基於任務的異步模式(TAP),推薦使用spring

4、C# 5.0 新特性——Async和Await使異步編程更簡單編程

1、引言

  當使用APM的時候,首先咱們要先定義用來包裝回調方法的委託,這樣不免有點繁瑣, 然而使用EAP的時候,咱們又須要實現Completed事件和Progress事件,上面兩種實現方式感受都有點繁瑣,同時微軟也意識到了這點,因此在.NET 4.0中提出了一個新的異步模式——基於任務的異步模式TAP(Task-based Asynchronous Pattern ),基於任務的異步模式 (TAP) 是基於 System.Threading.Tasks.Task 命名空間中的 System.Threading.Tasks.Task<TResult>System.Threading.Tasks 類型,這些類型用於表示任意異步操做。是用於新開發的建議的異步設計模式。設計模式

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

當看到類中存在TaskAsync爲後綴的方法時就表明該類實現了TAP, 而且基於任務的異步模式一樣也支持異步操做的取消進度的報告的功能。在TAP實現中,咱們只須要經過向異步方法傳入CancellationToken 參數,由於在異步方法內部會對這個參數的IsCancellationRequested屬性進行監控,當異步方法收到一個取消請求時,異步方法將會退出執行。在TAP中,咱們能夠經過IProgress<T>接口來實現進度報告的功能。api

2、並行編程 - Task任務異步

計算密集型任務

例如,請考慮使用呈現圖像的異步方法。.任務的主體能夠輪詢取消標記,若是在呈現過程當中收到取消請求,代碼可提早退出。 此外,若是呈現啓動以前收到取消請求,你須要阻止呈現操做:async

internal Task<Bitmap> RenderAsync(ImageData data, CancellationToken cancellationToken)
{
    return Task.Run(() =>
    {
        var bmp = new Bitmap(data.Width, data.Height);
        for(int y=0; y<data.Height; y++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            for(int x=0; x<data.Width; x++)
            {
                // render pixel [x,y] into bmp
            }
        }
        return bmp;
    }, cancellationToken);
}

I/O 密集型任務:


假設你想建立一個將在指定時間段後完成的任務。  例如,你可能想延遲用戶界面中的活動。  System.Threading.Timer 類已提供在指定時間段後以異步方式調用委託的能力,而且你能夠經過使用 TaskCompletionSource<TResult>Task<TResult> 前端放在計時器上,例如:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
   {
       TaskCompletionSource<DateTimeOffset> tcs = null;
       Timer timer = null;

       timer = new Timer(delegate
       {
           timer.Dispose();
           tcs.TrySetResult(DateTimeOffset.UtcNow);
       }, null, Timeout.Infinite, Timeout.Infinite);

       tcs = new TaskCompletionSource<DateTimeOffset>(timer);
       timer.Change(millisecondsTimeout, Timeout.Infinite);
       return tcs.Task;
   }

從 .NET Framework 4.5 開始,Task.Delay 方法正是爲此而提供的,而且你能夠在另外一個異步方法內使用它。例如,若要實現異步輪詢循環:異步編程

public static async Task Poll(Uri url, CancellationToken cancellationToken, 
                              IProgress<bool> progress)
{
    while(true)
    {
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
        bool success = false;
        try
        {
            await DownloadStringAsync(url);
            success = true;
        }
        catch { /* ignore errors */ }
        progress.Report(success);
    }
}

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

就基於上專題中實現的程序用基於任務的異步模式來完成下。下面就讓咱們實現本身的異步方法(亮點爲只須要一個方法就能夠完成進度報告和異步操做取消的功能)函數

// 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);
    }
}


這樣只須要上面的一個方法,咱們就能夠完成上一專題中文件下載的程序,咱們只須要在下載按鈕的事件處理程序調用該方法和在暫停按鈕的事件處理程序調用CancellationTokenSource.Cancel方法便可,具體代碼爲:

#region 字段

private int DownloadSize = 0;
private string downloadPath = null;
private long totalSize = 0;
private FileStream filestream;

private CancellationTokenSource cts = null;
private Task task = null;

private SynchronizationContext sc;

#endregion 字段

#region 構造函數

public FileDownLoadForm()
{
    InitializeComponent();
    string url = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
    txbUrl.Text = url;
    this.btnPause.Enabled = false;

    // Get Total Size of the download file
    GetTotalSize();
    downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\" + Path.GetFileName(this.txbUrl.Text.Trim());
    if (File.Exists(downloadPath))
    {
        FileInfo fileInfo = new FileInfo(downloadPath);
        DownloadSize = (int)fileInfo.Length;
        if (DownloadSize == totalSize)
        {
            string message = "There is already a file with the same name, "
                    + "do you want to delete it? "
                    + "If not, please change the local path. ";
            var result = MessageBox.Show(
                message,
                "File name conflict: " + downloadPath,
                MessageBoxButtons.OKCancel);

            if (result == System.Windows.Forms.DialogResult.OK)
            {
                File.Delete(downloadPath);
            }
            else
            {
                progressBar1.Value = (int)((float)DownloadSize / (float)totalSize * 100);
                this.btnStart.Enabled = false;
                this.btnPause.Enabled = false;
            }
        }
    }
}

#endregion 構造函數

#region 方法

// 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();
}

// Get Total Size of File
private void GetTotalSize()
{
    HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
    HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.GetResponse();
    totalSize = response.ContentLength;
    response.Close();
}

4、與其餘異步模式和類型互操做

一、從 APM 到 TAP

可使用 TaskFactory<TResult>.FromAsync 方法來實現此操做的 TAP 包裝,以下所示:

public static Task<int> ReadAsync(this Stream stream, 
                                  byte[] buffer, int offset, 
                                  int count)
{
    if (stream == null) 
       throw new ArgumentNullException("stream");
    
    return Task<int>.Factory.FromAsync(stream.BeginRead, 
                                       stream.EndRead, buffer, 
                                       offset, count, null);
}

此實現相似於如下內容:

 public static Task<int> ReadAsync(this Stream stream, 
                                   byte [] buffer, int offset, 
                                   int count)
 {
    if (stream == null) 
        throw new ArgumentNullException("stream");

    var tcs = new TaskCompletionSource<int>();
    stream.BeginRead(buffer, offset, count, iar =>
                     {
                        try { 
                           tcs.TrySetResult(stream.EndRead(iar)); 
                        }
                        catch(OperationCanceledException) { 
                           tcs.TrySetCanceled(); 
                        }
                        catch(Exception exc) { 
                           tcs.TrySetException(exc); 
                        }
                     }, null);
    return tcs.Task;
}

二、從EAP到 TAP

public static Task<string> DownloadStringAsync(Uri url)
 {
     var tcs = new TaskCompletionSource<string>();
     var wc = new WebClient();
     wc.DownloadStringCompleted += (s,e) =>
         {
             if (e.Error != null) 
                tcs.TrySetException(e.Error);
             else if (e.Cancelled) 
                tcs.TrySetCanceled();
             else 
                tcs.TrySetResult(e.Result);
         };
     wc.DownloadStringAsync(url);
     return tcs.Task;
}

5、小結

  本專題關於TAP的內容就介紹到這裏了,本專題主要以實現一個文件下載程序要講述基於任務的異步模式所帶來的簡便,這個也是.NET 4.0中提出TAP的緣由所在吧,最後介紹了TAP與APM和EAP模式之間的轉化,經過這部分你們能夠清楚知道之前的異步實現如何向新的異步模式的遷移,以及從他們的轉換實現代碼中也能夠比較他們之間的不一樣。然而在.NET 4.5中,微軟對異步編程又作了更好的支持——提供了async和await兩個關鍵字,這兩個關鍵字使咱們異步編程如同步編程同樣的簡單,完全改變了實現異步編程所面臨的委託回調,跨線程訪問控件等問題,具體這部份內容,我將在下個專題中爲你們介紹。

相關文章
相關標籤/搜索