把目前看到的資訊做個總結,分幾篇文章來寫
從同步開始
假設我們要抓取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的方式來持續讀取資料。而由於這樣,例外處理幾乎不可行。程式碼寫的支離破碎,難怪一堆人寧可寫同步版本。