把目前看到的資訊做個總結,分幾篇文章來寫
從同步開始
假設我們要抓取Web Response,可以這樣寫。
lang: c#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | static int BUFFER_SIZE = 1024; static void getContentFromURI(string uri) {     WebRequest request = WebRequest.Create(uri);     WebResponse response = request.GetResponse();     Stream stream = response.GetResponseStream();     byte[] buffer = new byte[BUFFER_SIZE];     using (stream)     {         while (true)         {             int actualRead = stream.Read(buffer, 0, BUFFER_SIZE);             if (actualRead != 0)             {                 string partialContent = Encoding.Default.GetString(buffer, 0, actualRead);                 Console.WriteLine(partialContent);             }             else             {                 break;             }         }     } }
   | 
 
以上都是同步動作,如果有個Thread呼叫此函數,外面的Thread必須等待這個函數執行結束,才能繼續往下走。如果是UI Thread的話,通常會出現漏斗符號,然後整個UI僵住的情形。
放到Thread
解決方案之一是把耗時的工作丟到Thread之後,外面呼叫的就能繼續向下執行。
lang: c#1 2 3 4 5 6 7
   | static void ThreadStart() {     string uri = "http://www.facebook.com";     getContentFromURI(uri); } Thread thd = new Thread(ThreadStart); thd.Start();
   | 
 
更甚一點的是用ThreadPool來,減少Thread的開銷
lang: c#1
   | ThreadPool.QueueUserWorkItem(state => ThreadStart());
   | 
 
不過這還是無法完全解決問題,這邊的行為是屬於IO Bounded Operation,不需要CPU計算能力,指需要完成之後,發個中斷通知CPU事件完成就好,使用Thread的解決方案還是不夠好。而.Net的IO Operation幾乎都有提供同步跟非同步的版本,就從這點開始。
.Net 1.0, BeginXXX / EndXXX
如同上面所說,幾乎所有IO operation都有非同步版本,而其命名都是BeginXXX / EndXXX開始,因此我們可以把程式改寫成這樣。
lang: c#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
   | static void getContentFromURI(string uri) {     WebRequest myWebRequest = WebRequest.Create(uri);
      myWebRequest.BeginGetResponse(ar =>     {         WebResponse response = myWebRequest.EndGetResponse(ar);         Stream stream = response.GetResponseStream();         byte[] buffer = new byte[BUFFER_SIZE];         using (stream)         {             while (true)             {                 int actualRead = stream.Read(buffer, 0, BUFFER_SIZE);                 if (actualRead != 0)                 {                     string partialContent = Encoding.Default.GetString(buffer, 0, actualRead);                     Console.WriteLine(partialContent);                 }                 else                 {                     break;                 }             }         }     }, null); }
   | 
 
程式碼被切成兩段,在GetResponse之前,跟GetResponse之後,相較於同步的版本,相差沒說很大。
談一下Exception部分,同步的版本指需要一個try-catch,而非同步的需要兩個,這邊變得很麻煩。
更進一步將Read的部份變成非同步的版本,程式碼會變成這樣。
lang: c#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
   | static void ReadHelper(Stream stream) { 	byte[] buffer = new byte[BUFFER_SIZE]; 	stream.BeginRead(buffer, 0, BUFFER_SIZE, ar => 	{             int actualRead = stream.EndRead(ar);             if (actualRead != 0)             {                 string partialContent = Encoding.Default.GetString(buffer, 0, actualRead);                 Console.WriteLine(partialContent);                 ReadHelper(stream);             }             else             {                 stream.Close();             }         }, null); } static void getContentFromURI(string uri) { 	WebRequest myWebRequest = WebRequest.Create(uri);
  	myWebRequest.BeginGetResponse(ar => 	{             WebResponse response = myWebRequest.EndGetResponse(ar);             Stream stream = response.GetResponseStream();             ReadHelper(stream); 	}, null); }
   | 
 
這邊可以看到,用using釋放資源的方式已不可行,同樣的,用for/while的方式也不可行,必須使用Recursive的方式來持續讀取資料。而由於這樣,例外處理幾乎不可行。程式碼寫的支離破碎,難怪一堆人寧可寫同步版本。