《learning hard C#学习笔记》读书笔记(20)异步编程

 

20.1 什么是异步编程异步编程就是把耗时的操作放进一个单独的线程中进行处理。

20.2 同步方式存在的问题

 
  1. namespace 异步编程  
  2. {  
  3.     public partial class Form1 : Form  
  4.     {  
  5.         public Form1()  
  6.         {  
  7.             InitializeComponent();  
  8.             txbUrl.Text = "http://dldir1.qq.com/qqfile/qq/QQ8.7/19113/QQ8.7.exe";   
  9.         }  
  10.    
  11.         private void btnDownLoad_Click(object sender, EventArgs e)  
  12.         {  
  13.             rtbState.Text = "下载中.....";  
  14.             if (txbUrl.Text == string.Empty)  
  15.             {  
  16.                 MessageBox.Show("请先输入下载地址!");  
  17.                 return;  
  18.             }  
  19.             DownLoadFileSync(txbUrl.Text.Trim());  
  20.         }  
  21.   
  22.         public void DownLoadFileSync(string url)  
  23.         {  
  24.             int BufferSize = 2084;  
  25.             byte[] BufferRead = new byte[BufferSize];  
  26.             string savePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\我的QQ.exe";  
  27.             FileStream fileStream = null;  
  28.             HttpWebResponse myWebResponse = null;  
  29.             if (File.Exists(savePath))  
  30.             {  
  31.                 File.Delete(savePath);  
  32.             }  
  33.             fileStream = new FileStream(savePath, FileMode.OpenOrCreate);  
  34.   
  35.             try  
  36.             {  
  37.                 HttpWebRequest myHTTpWebRequest = (HttpWebRequest) WebRequest.Create(url);  
  38.                 if (myHTTpWebRequest != null)  
  39.                 {  
  40.                     myWebResponse = (HttpWebResponse) myHTTpWebRequest.GetResponse();  
  41.                     Stream responseStream = myWebResponse.GetResponseStream();  
  42.                     int readSize = responseStream.Read(BufferRead, 0, BufferSize);  
  43.                     while (readSize > 0)  
  44.                     {  
  45.                         fileStream.Write(BufferRead, 0, readSize);  
  46.                         readSize = responseStream.Read(BufferRead, 0, BufferSize);  
  47.                     }  
  48.                     rtbState.Text = "文件下载完成,文件大小为:" + fileStream.Length + "下载路径为:" + savePath;  
  49.                 }  
  50.             }  
  51.             catch (Exception ex)  
  52.             {  
  53.                 rtbState.Text = "下载异常,异常信息为:" + ex.Message;  
  54.             }  
  55.             finally  
  56.             {  
  57.                 if (myWebResponse != null)  
  58.                 {  
  59.                     myWebResponse.Close();  
  60.   
  61.                 }  
  62.                 if (fileStream !=null)  
  63.                 {  
  64.                     fileStream.Close();  
  65.                 }  
  66.             }  
  67.         }  
  68.     }  
  69. }  
虽然完成了文件下载功能,但却带来不好的用户体验,
点“下载”按钮后,程序处于“假死”状态。而且,“下载中....”也没有显示在界面中。
主线程忙于执行下载操作,无法空出时间来绘制窗体,导致在下载过程中出现“假死”现象。
异步编程可以很好的解决这个问题。

20.3 异步编程模型——APM(.NET不推荐)

         APM 是 Asynchronous Programming Mode (异步编程模式)的缩写。(在.NET Framework 4 和更早的版本中
        它允许程序用更少的线程去执行更多的操作。
        要分辨某个类是否实现异步编程模式,主要看该类是否实现了返回类型为 IAsyncResult 接口的 Beginxxx 和 Endxxx 方法。

1.Beginxxx 方法——开始执行异步操作

       在要获取文件中内容时,通常使用 FileStream 的同步方法 Read 进行读取,定义:
public abstract int Read(byte[] array, int offset, int count);
       该方法从文件流中读取字节块,然后将该数据写入给定的字节数组 array 中。这里 array 表示读取的字节块要被写入的地方, offset 表示从文件流读取字节的开始位置, count 表示最多读取的字节数。
       读取大文件时,调用 Read 方法会阻塞UI线程,导致文件没有读取前窗体无法响应。为了解决这个情况,.NET1.0就提出了异步编程模型,并为 FileStream 类提供了异步模型方法,即 BeginRead 方法。该方法会异步执行读取,并返回 IAsyncResult 接口的对象(该对象存储异步操作信息)。
public virtual IAsyncResult BeginRead(
	byte[] buffer,
	int offset,
	int count,
	AsyncCallback callback,
	object state
)

该方法前面3个参数和同步方法 Read 一致,后两个参数 callback 和 state 则不同。

callbackType: System.AsyncCallback

异步操作完成后需要回调的方法,该方法必须匹配 AsyncCallback委托类型。

stateType: System.Object

传递给回调方法的对象,在回调方法中,可以查询 IAsyncResult 接口的 AsyncState 属性来读取该对象。

2.Endxxx 方法 ——结束异步操作

调用完 Beginxxx 方法后,还需要调用 Endxxx 方法来获取操作返回的结果。
Endxxx 返回的类型与同步方法相同,如 FileStream EndRead 方法会返回一个 Int32 类型,代表从文件流中实际读取的字节数。
 
APM给出了4种方式来访问异步操作所得到的结果。
  1. 在调用 Beginxxx 方法的线程上调用 Endxxx 方法来得到异步操作的结果。然而这种方式会阻塞调用线程,使其一直挂起,直至操作完成。

  2. 在调用 Beginxxx 方法的线程查询 IAsyncResult AsyncWaitHandle 属性,从而得到 WaitHandle 对象,接着调用该对象 WaitOne 方法来堵塞线程并等待操作完成,最后调用 Endxxx 方法获得操作结果。

  3. 在调用 Beginxxx 方法的线程上循环查询 IAsyncResult IsComplete 属性,操作完成后调用 Endxxx 方法获得操作结果。

  4. 使用 AsyncCallback 委托来指定操作完成要调用的方法,在回调方法中调用 Endxxx 方法来获得异步操作返回的结果。

上面4种,前3种会堵塞调用线程。

第1种方式:

  1. public void DownLoadFileSync(string url)  

  2.         {  

  3.             int BufferSize = 2084;  

  4.             byte[] BufferRead = new byte[BufferSize];  

  5.             string savePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\我的QQ.exe";  

  6.             FileStream fileStream = null;  

  7.             HttpWebResponse myWebResponse = null;  

  8.             if (File.Exists(savePath))  

  9.             {  

  10.                 File.Delete(savePath);  

  11.             }  

  12.             fileStream = new FileStream(savePath, FileMode.OpenOrCreate);  

  13.   

  14.             try  

  15.             {  

  16.                 HttpWebRequest myHTTpWebRequest = (HttpWebRequest) WebRequest.Create(url);  

  17.                 if (myHTTpWebRequest != null)  

  18.                 {  

  19.                     //myWebResponse = (HttpWebResponse)myHTTpWebRequest.GetResponse();  

  20.                     IAsyncResult result = myHTTpWebRequest.BeginGetResponse(nullnull);//异步请求  

  21.                     myWebResponse = (HttpWebResponse)myHTTpWebRequest.EndGetResponse(result);  

  22.   

  23.                     Stream responseStream = myWebResponse.GetResponseStream();  

  24.                     int readSize = responseStream.Read(BufferRead, 0, BufferSize);  

  25.                     while (readSize > 0)  

  26.                     {  

  27.                           

  28.                         fileStream.Write(BufferRead, 0, readSize);  

  29.                         readSize = responseStream.Read(BufferRead, 0, BufferSize);  

  30.                     }  

  31.                     rtbState.Text = "文件下载完成,文件大小为:" + fileStream.Length + "下载路径为:" + savePath;  

  32.                 }  

  33.             }  

  34.             catch (Exception ex)  

  35.             {  

  36.                 rtbState.Text = "下载异常,异常信息为:" + ex.Message;  

  37.             }  

  38.             finally  

  39.             {  

  40.                 if (myWebResponse != null)  

  41.                 {  

  42.                     myWebResponse.Close();  

  43.   

  44.                 }  

  45.                 if (fileStream !=null)  

  46.                 {  

  47.                     fileStream.Close();  

  48.                 }  

  49.             }  

  50.         }  

       DownLoadFileSync 方法通过调用 BeginGetResponse 方法来异步请求资源,执行该方法后立即返回UI线程。UI线程继续执行代码,遇到 EndGetResponse 方法,此方法堵塞UI线程,使得效果和同步方法实现一样。

  1. namespace 异步编程  

  2. {  

  3.     public partial class Form1 : Form  

  4.     {  

  5.         public Form1()  

  6.         {  

  7.             InitializeComponent();  

  8.             txbUrl.Text = "http://dldir1.qq.com/qqfile/qq/QQ8.7/19113/QQ8.7.exe";  

  9.         }  

  10.   

  11.         private delegate string AsyncMethodCaller(string fileurl);  

  12.   

  13.         private SynchronizationContext sc;  

  14.   

  15.         private void btnDownLoad_Click(object sender, EventArgs e)  

  16.         {  

  17.             rtbState.Text = "下载中.....";  

  18.             btnDownLoad.Enabled = false;  

  19.             if (txbUrl.Text == string.Empty)  

  20.             {  

  21.                 MessageBox.Show("请先输入下载地址!");  

  22.                 return;  

  23.             }  

  24.             //DownLoadFileSync(txbUrl.Text.Trim());  

  25.             sc = SynchronizationContext.Current;  

  26.             AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);  

  27.             methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);  

  28.         }  

  29.   

  30.         private void GetResult(IAsyncResult result)  

  31.         {  

  32.             AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;  

  33.             string returning = caller.EndInvoke(result);  

  34.             sc.Post(ShowState, returning);  

  35.         }  

  36.   

  37.         private void ShowState(object result)  

  38.         {  

  39.             rtbState.Text = result.ToString();  

  40.             btnDownLoad.Enabled = true;  

  41.         }  

  42.   

  43.         public string DownLoadFileSync(string url)  

  44.         {  

  45.             int BufferSize = 2084;  

  46.             byte[] BufferRead = new byte[BufferSize];  

  47.             string savePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\我的QQ.exe";  

  48.             FileStream fileStream = null;  

  49.             HttpWebResponse myWebResponse = null;  

  50.             if (File.Exists(savePath))  

  51.             {  

  52.                 File.Delete(savePath);  

  53.             }  

  54.             fileStream = new FileStream(savePath, FileMode.OpenOrCreate);  

  55.   

  56.             try  

  57.             {  

  58.                 HttpWebRequest myHTTpWebRequest = (HttpWebRequest)WebRequest.Create(url);  

  59.                 if (myHTTpWebRequest != null)  

  60.                 {  

  61.                     myWebResponse = (HttpWebResponse)myHTTpWebRequest.GetResponse();  

  62.                     Stream responseStream = myWebResponse.GetResponseStream();  

  63.                     int readSize = responseStream.Read(BufferRead, 0, BufferSize);  

  64.                     while (readSize > 0)  

  65.                     {  

  66.   

  67.                         fileStream.Write(BufferRead, 0, readSize);  

  68.                         readSize = responseStream.Read(BufferRead, 0, BufferSize);  

  69.                     }  

  70.                 }  

  71.                 return  string.Format("文件下载完成,文件大小为:" + fileStream.Length + "下载路径为:" + savePath);  

  72.             }  

  73.             catch (Exception ex)  

  74.             {  

  75.                 return  string.Format("下载异常,异常信息为:" + ex.Message);  

  76.             }  

  77.             finally  

  78.             {  

  79.                 if (myWebResponse != null)  

  80.                 {  

  81.                     myWebResponse.Close();  

  82.   

  83.                 }  

  84.                 if (fileStream != null)  

  85.                 {  

  86.                     fileStream.Close();  

  87.                 }  

  88.             }  

  89.         }  

  90.     }  

  91. }  

 

 

20.4 异步编程模型——EAP(.NET不推荐)

       APM同样存在一些明显的问题,如不支持对异步操作的取消以及不能提供下载进度报告等。

微软.NET2.0发布提出了一个新的异步编程模式——基于事件的异步模式,EAP(Event-based Asynchronous Pattern)。

       实现EPA的类具有一个或多个以 Async 为后缀的方法,以及对应的 Completed 事件,并且这些类支持异步方法的取消和进度报告。在.NET类库中只有部分类实现了EPA,共17个。

System.Object的派生类型:

  System.Activies.WorkflowInvoke  

  System.Deployment.Application.ApplicationDeployment

  System.Deployment.Application.InPlaceHosingManager

  System.Net.Mail.SmtpClient

  System.Net.PeerToPeer.PeerNameResolver

  System.Net.PeerToPeer.Collaboration.ContactManager

  System.Net.PeerToPeer.Collaboration.Peer

  System.Net.PeerToPeer.Collaboration.PeerContact

  System.Net.PeerToPeer.Collaboration.PeerNearMe

  System.ServiceModel.Activities.WorkflowControlClient

  System.ServiceModel.Discovery.AnnoucementClient

  System.ServiceModel.Discovery.DiscoveryClient

System.ComponentModel.Component的派生类型:

System.ComponentModel.BackgroundWorker

System.Media.SoundPlay

System.Net.WebClient

System.Net.NetworkInformation.Ping

System.Windows.Forms.PictureBox(继承于Control类,Control类派生于Component类)

使用最多的莫过于 BackgroundWorker 类了,该类经常被用于实现网络文件下载功能。

BackgroundWorker类

 

公共属性

 

属性名

说明

CancellationPending

获取一个值,指示应用程序是否已请求取消后台操作

IsBusy

获取一个值,指示 BackgroundWorker 是否正在运行异步操作。

WorkReportsProgress

获取或设置一个值,该值指示 BackgroundWorker 能否报告进度更新。

WorkerSupportsCancellation

获取或设置一个值,该值指示 BackgroundWorker 是否支持异步取消。

公共方法

 

名称

说明

CancelAsync

请求取消挂起的后台操作。

ReportProgress

引发 ProgressChanged 事件(官方这样解释我就要信?)

RunWorkerAsync

开始执行后台操作。

公共事件

 

名称

说明

DoWork

调用 RunWorkerAsync 时发生(官方是这么解释的,你想知道为什么调用RunWorkerAsync方法就会触发DoWork事件吗?

ProgressChanged

调用ReportProgress时发生(官方是这么解释的,你想知道为什么调用ReportProgress方法就会触发ProgressChanged事件吗?)

RunWorkerCompleted

当后台操作已完成、被取消或引发异常时发生。

 

 

  1. public partial class Form1 : Form  

  2.     {  

  3.         public int DownloadSize = 0;  

  4.         public string downloadPath = null;  

  5.         long totalSize = 0;  

  6.         const int BufferSize = 2048;  

  7.         byte[] BufferRead = new byte[BufferSize];  

  8.         FileStream filestream = null;  

  9.         HttpWebResponse myWebResponse = null;  

  10.   

  11.         public Form1()  

  12.         {  

  13.             InitializeComponent();  

  14.             string url = "http://dldir1.qq.com/qqfile/qq/QQ8.7/19113/QQ8.7.exe";  

  15.   

  16.             txbUrl.Text = url;  

  17.             this.btnPause.Enabled = false;  

  18.   

  19.             GetTotalSize();  

  20.             downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\" + Path.GetFileName(this.txbUrl.Text.Trim());  

     

  21.             if (File.Exists(downloadPath))  

  22.             {  

  23.                 FileInfo fileInfo = new FileInfo(downloadPath);  

  24.                 DownloadSize = (int)fileInfo.Length;  

  25.                 progressBar1.Value = (int)((float)DownloadSize / (float)totalSize * 100);  

  26.             }  

  27.   

  28.             // 使BackgroundWorker类支持ReportProgress和ReportProgress操作  

  29.             bgWorkerFileDownload.WorkerReportsProgress = true;  

  30.             bgWorkerFileDownload.WorkerSupportsCancellation = true;  

  31.         }  

  32.   

  33.         private void btnDownLoad_Click(object sender, EventArgs e)  

  34.         {  

  35.             if (bgWorkerFileDownload.IsBusy != true)  

  36.             {  

  37.                 bgWorkerFileDownload.RunWorkerAsync();  

  38.   

  39.                 filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);  

  40.   

  41.                 filestream.Seek(DownloadSize, SeekOrigin.Begin);  

  42.                 this.btnDownLoad.Enabled = false;  

  43.                 this.btnPause.Enabled = true;  

  44.             }  

  45.             else  

  46.             {  

  47.                 MessageBox.Show("正在执行操作,请稍后");  

  48.             }  

  49.         }  

  50.   

  51.         private void btnPause_Click(object sender, EventArgs e)  

  52.         {  

  53.             if (bgWorkerFileDownload.IsBusy && bgWorkerFileDownload.WorkerSupportsCancellation == true)  

  54.             {  

  55.                 bgWorkerFileDownload.CancelAsync(); // 取消下载操作  

  56.             }  

  57.         }  

  58.   

  59.         private void bgWorkerFileDownload_DoWork(object sender, DoWorkEventArgs e)  

  60.         {  

  61.             BackgroundWorker bgworker = sender as BackgroundWorker;  

  62.             try  

  63.             {  

  64.                 // Do the DownLoad operation  

  65.                 // Initialize an HttpWebRequest object  

  66.                 HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());  

  67.   

  68.                 // If the part of the file have been downloaded,   

  69.                 // The server should start sending data from the DownloadSize to the end of the data in the HTTP entity.  

  70.                 if (DownloadSize != 0)  

  71.                 {  

  72.                     myHttpWebRequest.AddRange(DownloadSize);  

  73.                 }  

  74.   

  75.                 // assign HttpWebRequest instance to its request field.  

  76.                 myWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();  

  77.                 Stream responseStream = myWebResponse.GetResponseStream();  

  78.                 int readSize = 0;  

  79.                 while (true)  

  80.                 {  

  81.                     if (bgworker.CancellationPending == true)  

  82.                     {  

  83.                         e.Cancel = true;  

  84.                         break;  

  85.                     }  

  86.   

  87.                     readSize = responseStream.Read(BufferRead, 0, BufferRead.Length);  

  88.                     if (readSize > 0)  

  89.                     {  

  90.                         DownloadSize += readSize;  

  91.                         int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);  

  92.                         filestream.Write(BufferRead, 0, readSize);  

  93.   

  94.                         // 报告进度,引发ProgressChanged事件的发生  

  95.                         bgworker.ReportProgress(percentComplete);  

  96.                     }  

  97.                     else  

  98.                     {  

  99.                         break;  

  100.                     }  

  101.                 }  

  102.             }  

  103.             catch  

  104.             {  

  105.                 throw;  

  106.             }  

  107.         }  

  108.   

  109.         private void bgWorkerFileDownload_ProgressChanged(object sender, ProgressChangedEventArgs e)  

  110.         {  

  111.             this.progressBar1.Value = e.ProgressPercentage;  

  112.         }  

  113.   

  114.         private void bgWorkerFileDownload_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)  

  115.         {  

  116.             if (e.Error != null)  

  117.             {  

  118.                 MessageBox.Show(e.Error.Message);  

  119.                 myWebResponse.Close();  

  120.             }  

  121.             else if (e.Cancelled)  

  122.             {  

  123.                 MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}\n 已经下载的字节数为: {1}字节", downloadPath, DownloadSize));  

  124.                 myWebResponse.Close();  

  125.                 filestream.Close();  

  126.                 filestream.Close();  

  127.   

  128.                 this.btnDownLoad.Enabled = true;  

  129.                 this.btnPause.Enabled = false;  

  130.             }  

  131.             else  

  132.             {  

  133.                 MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节", downloadPath, totalSize));  

  134.   

  135.                 this.btnDownLoad.Enabled = false;  

  136.                 this.btnPause.Enabled = false;  

  137.                 myWebResponse.Close();  

  138.                 filestream.Close();  

  139.             }  

  140.         }  

  141.          

  142.         private void GetTotalSize() // 获得文件总大小  

  143.         {  

  144.             HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());  

  145.             HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.GetResponse();  

  146.             totalSize = response.ContentLength;  

  147.             response.Close();  

  148.         }  

  149.     }  

 

[你必须知道的异步编程]——基于事件的异步编程模式

http://www.cnblogs.com/zhili/archive/2013/05/11/EAP.html

 

20.5 异步编程模型——TAP

       前2种模式在.NET中被标识为不推荐使用的实现方式。

在.NET 4.0中,微软提供更简单的异步方式——基于任务的异步模式,即TAP。

       该模式主要使用 System.Threading.Tasks.Task和Task<T> 类来完成异步编程,相对于前面两种异步模式来讲,TAP使异步编程模式更加简单(因为这里我们只需要关注 Task 这个类的使用),同时TAP也是微软推荐使用的异步编程模式。

        基于任务的异步模式,只使用一个方法就能表示异步操作的开始和完成。只需要一个以 TaskAsync 为后缀的方法,通过向该方法传入 CancellationToken 参数,就可以完成异步编程了。而且还可以通过 IProgress<T> 接口来实现进度报告的功能。
 
 

20.6 C#5.0 中的 async 和 await

 
太大段代码,还是看看《C#本质论》18章 和《精通C#(第6版)》19章
 
 
 





posted @ 2016-12-20 10:29  【唐】三三  阅读(560)  评论(0编辑  收藏  举报