Ø 前言
C# 異步委托也是屬於異步編程中的一種,可以稱為 Asynchronous Programming(異步編程)或者 Asynchronous Programming Model(異步編程模型),因為這是實現異步編程的模式。委托是 C#1.0 就有的特性,並且 .NET v1.0 同時也伴隨有 AsyncCallback、IAsyncResult 等類/接口的出現,所以所有的 .NET 版本中都是支持的。
1. 什么是異步委托
1) 異步委托是采用異步回調的方式實現異步執行,當使用委托異步執行某個方法時,將從線程池中取出一個線程去執行該方法。
2) 當執行完成后則調用 AsyncCallback 委托指定的方法,完成異步回調。
3) 開始執行一個異步委托后,可以使用4種方式等待異步執行完成:
1. 開啟異步委托后,BeginInvoke() 方法將返回一個實現了 IAsyncResult 接口的 System.Runtime.Remoting.Messaging.AsyncResult 對象。使用該對象的 AsyncWaitHandle 屬性,並調用 WaitOne() 方法,該方法會阻塞當前線程,直到收到信號(異步委托方法執行完成)。
2. 調用委托對象的 EndInvoke() 方法,需要傳遞一個 AsyncResult 對象,該方法也用於獲取異步委托的返回值,所以這種方式也會阻塞當前線程。
3. 使用 IAsyncResult.IsCompleted 屬性,判斷是否執行完成。該屬性在異步委托方法執行完成時為 true.
4. 【推薦】使用異步回調委托的方式,當異步委托方法執行完成后調用,如果在不需要非要等到異步完成時獲取返回結果的情況下,推薦使用該方式。
2. 下面分別使用這四種方式等待
首先,定義四個委托類型。
public delegate void MyDelegate1();
public delegate string MyDelegate2();
public delegate void MyDelegate3(string str);
public delegate int MyDelegate4(int num1, int num2);
1) 使用 WaitOne() 方法(匿名方法)
/// <summary>
/// 使用 WaitOne() 方法(匿名方法)。
/// </summary>
public void AsyncDelegateTest1()
{
WriteLine("AsyncDelegateTest1() 方法開始執行,線程Id:{0}", GetThreadId());
MyDelegate1 d1 = new MyDelegate1(delegate()
{
WriteLine("匿名方法開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
Thread.Sleep(3000);
WriteLine("匿名方法結束執行,線程Id:{0}", GetThreadId());
});
IAsyncResult ar = d1.BeginInvoke(null, null);
ar.AsyncWaitHandle.WaitOne(); //這里將阻塞線程,直到收到信號(異步方法執行完成)
WriteLine("AsyncDelegateTest1() 方法結束執行,線程Id:{0},{1}", GetThreadId(), GetTime());
}
運行以上代碼:
2) 使用委托對象的 EndInvoke() 方法(匿名方法)
/// <summary>
/// 使用委托對象的 EndInvoke() 方法(匿名方法)。
/// </summary>
public void AsyncDelegateTest2()
{
WriteLine("AsyncDelegateTest2() 方法開始執行,線程Id:{0}", GetThreadId());
MyDelegate2 d2 = new MyDelegate2(delegate()
{
WriteLine("匿名方法開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
Thread.Sleep(3000);
WriteLine("匿名方法結束執行,線程Id:{0}", GetThreadId());
return System.Reflection.MethodBase.GetCurrentMethod().Name;
});
IAsyncResult ar = d2.BeginInvoke(null, null);
string result = d2.EndInvoke(ar); //這里也將阻塞線程,直到異步方法執行完成
WriteLine("AsyncDelegateTest2() 方法結束執行,{0},異步委托返回結果:{1}", GetTime(), result);
}
運行以上代碼:
3) 使用 IAsyncResult.IsCompleted 屬性(Lambda 表達式)
/// <summary>
/// 使用 IAsyncResult.IsCompleted 屬性(Lambda 表達式)。
/// </summary>
public void AsyncDelegateTest3()
{
WriteLine("AsyncDelegateTest3() 方法開始執行,線程Id:{0}", GetThreadId());
MyDelegate3 d3 = new MyDelegate3((str) =>
{
WriteLine("Lambda 表達式開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
Thread.Sleep(3000);
WriteLine("Lambda 表達式結束執行,str:{0}", str);
});
IAsyncResult ar = d3.BeginInvoke("這是一段話!", null, null);
while (!ar.IsCompleted) //標記是否完成(其實與直接調 EndInvoke() 方法沒什么區別)
{ }
WriteLine("AsyncDelegateTest3() 方法結束執行,線程Id:{0},{1}", GetThreadId(), GetTime());
}
運行以上代碼:
4) 【推薦】使用異步回調委托
/// <summary>
/// 【推薦】使用異步回調委托。
/// </summary>
public void AsyncDelegateTest4()
{
WriteLine("AsyncDelegateTest4() 方法開始執行,線程Id:{0}", GetThreadId());
MyDelegate4 d4 = new MyDelegate4(Add);
//這里必須將第二個參數(委托對象)傳入,否則異步回調中 IAsyncResult.AsyncState 屬性將為 null.
IAsyncResult ar = d4.BeginInvoke(22, 36, new AsyncCallback(AddCallback), d4);
WriteLine("AsyncDelegateTest4() 方法結束執行,線程Id:{0}", GetThreadId());
}
public int Add(int num1, int num2)
{
WriteLine("Add() 方法開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
Thread.Sleep(3000);
WriteLine("Add() 方法結束執行,線程Id:{0}", GetThreadId());
return num1 + num2;
}
public void AddCallback(IAsyncResult ar)
{
WriteLine("AddCallback() 方法開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
MyDelegate4 d4 = ar.AsyncState as MyDelegate4; //獲取委托對象
int result = d4.EndInvoke(ar); //這里並不會阻塞
WriteLine("AddCallback() 方法結束執行,計算結果:{0},{1}", result, GetTime());
}
運行以上代碼:
3. 下面,再來看下 C# 中一些常用基於異步回調的運用
1) 模擬 Web 請求,異步讀取響應流
/// <summary>
/// 異步獲取網頁 HTML 內容。
/// </summary>
public void AsyncGetHtmlString()
{
WriteLine("AsyncGetHtmlString() 方法開始執行,線程Id:{0},{1}", GetThreadId(), GetTime());
WebRequest request = WebRequest.Create("http://www.cnblogs.com/abeam/");
request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
WriteLine("AsyncGetHtmlString() 方法結束執行,線程Id:{0}", GetThreadId());
}
public async void ResponseCallback(IAsyncResult ar)
{
WriteLine("ResponseCallback() 方法開始執行(此時已經獲得響應),線程Id:{0},{1}", GetThreadId(), GetTime());
WebRequest request = ar.AsyncState as WebRequest;
using (WebResponse response = request.EndGetResponse(ar))
{
using (var stream = response.GetResponseStream())
{
WriteLine("開始異步讀取,線程Id:{0},{1}", GetThreadId(), GetTime());
WriteLine("響應的 HTML 內容:");
int count, totalCount = 0;
//1. 同步讀取響應流
using (var sr = new System.IO.StreamReader(stream, Encoding.UTF8))
{
char[] chars = new char[256];
while ((count = sr.Read(chars, 0, chars.Length)) > 0)
{
totalCount += count;
if (totalCount <= chars.Length) //太多屏幕容不下
{
string content = new string(chars, 0, count);
WriteLine(content);
WriteLine("同步讀取流線程Id:{0}", GetThreadId());
}
}
}
WriteLine("響應的 HTML 總字符數:{0}", totalCount);
//2. 異步讀取響應流
/*
* byte[] buffer = new byte[stream.Length];
* int totalCount = await stream.ReadAsync(buffer, 0, buffer.Length);
* 不能使用 stream.Length,因為 stream 是一種 System.Net.ConnectStream,否者將報異常:
* 未處理System.NotSupportedException
* Message: “System.NotSupportedException”類型的未經處理的異常在 mscorlib.dll 中發生
* 其他信息: 此流不支持查找操作。
*/
byte[] buffer = new byte[1024];
while ((count = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
totalCount += count;
if (totalCount <= 1024) //太多屏幕容不下
{
string content = Encoding.UTF8.GetString(buffer);
Write(content);
}
WriteLine();
Write("異步讀取流線程Id:{0}", GetThreadId());
}
WriteLine();
WriteLine("響應的 HTML 總字節數:{0}", totalCount);
}
}
}
下面是兩種讀取方式的結果:
Ø 總結
1. 異步委托主要使用 BeginInvoke() 方法開啟異步委托,該方法傳入一個回調委托 AsyncCallback 對象。
2. BeginInvoke() 返回一個實現了 IAsyncResult 接口的對象,可以使用該對象的 AsyncWaitHandle.WaitOne() 方法和 IsCompleted 屬性判斷異步是否完成。
3. 同樣 AsyncCallback 委托的簽名也有個 IAsyncResult 參數,該委托將在異步調用完成時執行。
4. 需要獲取異步委托的返回結果,都必須調用 EndInvoke() 方法。

![clip_image002[4] clip_image002[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMwNDEzNTgtMTEzNDI5OTUyNC5qcGc=.png)
![clip_image004[4] clip_image004[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMwNTUwMjEtNTQ5OTYxNjg0LmpwZw==.png)
![clip_image005[4] clip_image005[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMxMDc5ODAtMzY5MDEwMDM4LnBuZw==.png)
![clip_image007[4] clip_image007[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMxMjA4OTctMTE5NzQwOTMyMi5qcGc=.png)
![clip_image009[4] clip_image009[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMxMzk2NDEtODQwMTc0NzE5LmpwZw==.png)
![clip_image011[4] clip_image011[4]](/image/aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvNjU0OTIwLzIwMTkwNy82NTQ5MjAtMjAxOTA3MjIxOTMxNTE2MjktMTcyOTY2MjI2MC5qcGc=.png)