Ø 前言編程
C# 異步委託也是屬於異步編程中的一種,能夠稱爲 Asynchronous Programming(異步編程)或者 Asynchronous Programming Model(異步編程模型),由於這是實現異步編程的模式。委託是 C#1.0 就有的特性,而且 .NET v1.0 同時也伴隨有 AsyncCallback、IAsyncResult 等類/接口的出現,因此全部的 .NET 版本中都是支持的。異步
1. 什麼是異步委託async
1) 異步委託是採用異步回調的方式實現異步執行,當使用委託異步執行某個方法時,將從線程池中取出一個線程去執行該方法。ide
2) 當執行完成後則調用 AsyncCallback 委託指定的方法,完成異步回調。異步編程
3) 開始執行一個異步委託後,能夠使用4種方式等待異步執行完成:spa
1. 開啓異步委託後,BeginInvoke() 方法將返回一個實現了 IAsyncResult 接口的 System.Runtime.Remoting.Messaging.AsyncResult 對象。使用該對象的 AsyncWaitHandle 屬性,並調用 WaitOne() 方法,該方法會阻塞當前線程,直到收到信號(異步委託方法執行完成)。線程
2. 調用委託對象的 EndInvoke() 方法,須要傳遞一個 AsyncResult 對象,該方法也用於獲取異步委託的返回值,因此這種方式也會阻塞當前線程。3d
3. 使用 IAsyncResult.IsCompleted 屬性,判斷是否執行完成。該屬性在異步委託方法執行完成時爲 true.orm
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() 方法。