0%

The Evolution of Async-Programming on .NET Platform -- Part 1

把目前看到的資訊做個總結,分幾篇文章來寫

從同步開始

假設我們要抓取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的方式來持續讀取資料。而由於這樣,例外處理幾乎不可行。程式碼寫的支離破碎,難怪一堆人寧可寫同步版本。