異步的核心: IAsyncResult
Asynchronous Programming Model
整個異步調用過程中都是圍繞IAsyncResult來進行的,大家可以看看上篇文章的例子,BeginXXX 返回這個對象,EndXXX接收這個對象來結束當前異步對象,下面我們來看看IAsyncResult 接口成員/和實現此接口的AsyncResult類成員(其中有些在上篇中已經涉及到)
IAsyncResult接口
1public interface IAsyncResult 2 { 3 WaitHandle AsyncWaitHandle { get; } //阻塞一個線程,直到一個或多個同步對象接收到信號 4 Boolean IsCompleted { get; } //判讀當前異步是否完成 5 Object AsyncState { get; } //獲取額外的參數值,請看上一篇文章的Code 4.3 6 Boolean CompletedSynchronously { get; } //幾乎沒有使用 7 }
AsyncResult類
1 public class AsyncResult : IAsyncResult, IMessageSink 2 { 3 //IAsyncResult 的實現 4 public virtual WaitHandle AsyncWaitHandle { get; } 5 public virtual bool IsCompleted { get; } 6 public virtual object AsyncState { get; } 7 public virtual bool CompletedSynchronously { get; } 8 9 // 其他一些重要的屬性 10 public bool EndInvokeCalled { get; set; } //檢驗是否調用了EndInvoke() 11 public virtual object AsyncDelegate { get; } //獲取原始的委托對象,可查看上一篇文章中的Code 4.1/4.2/5 12 }
注意:基本上都是只讀屬性
下面我們來看看異步的執行順序,並回顧下 IAsyncResult 下各個屬性的應用,如果還是不熟悉請看前2篇文章.
Code 1:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 6 7 AsyncTest test = new AsyncTest(); 8 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary; 9 //使用回調函數 10 AsyncCallback callback = new AsyncCallback(OnSalaryCallback); 11 IAsyncResult ar = del.BeginInvoke(100000, 15, 100000, callback, 2000); 12 13 DoAntherJob(); 14 Console.ReadLine(); // 讓黑屏等待,不會直接關閉..15 } 16 17 //開始其他工作.18 static void DoAntherJob() 19 { 20 Thread.Sleep(1000);//需要1秒才能完成這個工作,注1 21 Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 22 } 23 24 static void OnSalaryCallback(IAsyncResult asyncResult) 25 { 26 //通過AsyncState 獲取額外的參數.27 decimal para = (int)asyncResult.AsyncState; 28 29 //通過AsyncDelegate 獲取原始的委托對象 30 AsyncResult obj = (AsyncResult)asyncResult; 31 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = (MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate; 32 33 if (asyncResult.IsCompleted)// 判讀是否已經調用完成 34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 35 36 decimal val = del.EndInvoke(asyncResult); 37 38 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val + para, Thread.CurrentThread.ManagedThreadId); 39 } 40 } 41 42 public class AsyncTest 43 { 44 public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 對應YearlySalary方法 45 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus) 46 { 47 //模擬耗時/復雜的邏輯計算.48 Thread.Sleep(3000);//等待3秒,注2 49 return salary * monthCount + bonus; 50 } 51 }
圖1
我們看到DoAntherJob 比異步YearlySalary快2秒,看代碼中(注1)和(注2),兩個線程的執行結果
接下來,我們說說AsyncWaitHandle 屬性.他返回WaitHandle對象(System.Threading.WaitHandle), 他有3個重要的方法.WaitOne / WaitAny / WaitAll ,我們先來說下WaitOne,在Code1代碼基礎上只是增加了下面紅色部分.
1,WaitOne
Code 1.1
IAsyncResult ar = del.BeginInvoke(100000, 15, 100000, callback, 2000); //阻礙當前線程,直到異步調用結束.ar.AsyncWaitHandle.WaitOne(); //開始其他工作.DoAntherJob();
圖1.1
執行輸出,對比圖1我們可以看到執行的次序不一樣了(看時間),調用WaitOne,會阻礙當前線程,直到異步完成,才釋放當前的線程, WaitOne 提供了時間的重載版本WaitOne(int millisecondsTimeout)/ WaitOne(TimeSpan timeout);來判斷阻礙的時間.無參的版本是無限等待的(直到異步調用結束)
2, WaitAll
我們在Code1的代碼基礎上加上Hello的異步調用(使Main提供多個異步調用),注意紅色部分.
Code 1.2
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 6 7 AsyncTest test = new AsyncTest(); 8 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary; 9 MyThirdAsyncCode.AsyncTest.AsyncEventHandler asy = test.Hello; 10 11 IAsyncResult salayAsyc = del.BeginInvoke(100000, 15, 100000, OnSalaryCallback, null); 12 IAsyncResult helloAsyc = asy.BeginInvoke("Hello Andy", OnHelloCallback, null); 13 //把所有異步的句柄保存到WaitHandle 對象中 14 WaitHandle[] handles = { salayAsyc.AsyncWaitHandle, helloAsyc.AsyncWaitHandle }; 15 //阻礙當前線程,直到所有異步調用結束.16 WaitHandle.WaitAll(handles); 17 18 //開始其他工作.19 DoAntherJob(); 20 Console.ReadLine(); // 讓黑屏等待,不會直接關閉..21 } 22 static void DoAntherJob() 23 { 24 Thread.Sleep(1000);//需要1秒才能完成這個工作,注1 25 Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 26 } 27 static void OnSalaryCallback(IAsyncResult asyncResult) 28 { 29 //通過AsyncDelegate 獲取原始的委托對象 30 AsyncResult obj = (AsyncResult)asyncResult; 31 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = (MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate; 32 33 if (asyncResult.IsCompleted)// 判讀是否已經調用完成 34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 35 36 decimal val = del.EndInvoke(asyncResult); 37 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId); 38 } 39 40 static void OnHelloCallback(IAsyncResult asyncResult) 41 { 42 //通過AsyncDelegate 獲取原始的委托對象 43 AsyncResult obj = (AsyncResult)asyncResult; 44 MyThirdAsyncCode.AsyncTest.AsyncEventHandler del = (MyThirdAsyncCode.AsyncTest.AsyncEventHandler)obj.AsyncDelegate; 45 46 if (asyncResult.IsCompleted)// 判讀是否已經調用完成 47 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId); 48 49 string val = del.EndInvoke(asyncResult); 50 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId); 51 } 52 } 53 54 public class AsyncTest 55 { 56 public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 對應YearlySalary方法 57 public delegate string AsyncEventHandler(string name); // 對應Hello 方法 58 public string Hello(string name) 59 { 60 //模擬耗時/復雜的邏輯計算.等待5秒 61 Thread.Sleep(5000); 62 return "Hello:" + name; 63 } 64 public decimal YearlySalary(decimal salary, int monthCount, decimal bonus) 65 { 66 //模擬耗時/復雜的邏輯計算.67 Thread.Sleep(3000);//等待3秒 68 return salary * monthCount + bonus; 69 } 70 }
圖1.2
從圖1.2中可以看出,WaitAll會阻礙當前線程(主線程#10),等待所有異步的對象都執行完畢(耗時最長的異步),才釋放當前的線程,WaitAll/WaitAny的重載版本和WaitOne一樣.
3, WaitAny
和WaitAll 基本上是一樣的.我們可以使用 WaitAny 來指定某個/某幾個委托先等待,修改Code1.2紅色部分,使用WaitAny.
Code1.3
//把salayAsyc異步的句柄保存到WaitHandle 對象中
WaitHandle[] handles = { salayAsyc.AsyncWaitHandle };
//阻礙當前線程,直到所有異步調用結束.
WaitHandle.WaitAny(handles);
圖1.3
我們阻礙了DoAntherJob(#10)線程,直到Salary異步調用計算完成.同樣我們可以巧用這三個方法來改變我們方法執行的順序.
釋放資源
Code2
1static void OnSalaryCallback(IAsyncResult asyncResult) 2 { 3 //通過AsyncDelegate 獲取原始的委托對象 4 AsyncResult obj = (AsyncResult)asyncResult; 5 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = (MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate; 6 7 decimal val = del.EndInvoke(asyncResult); 8 asyncResult.AsyncWaitHandle.Close();//顯示的釋放資源 9 }
當開始調用BeginXXX後,就會創建一個新的AsyncResult對象.這個對象會構造一個WaitHandle句柄(通過AsyncWaitHandle訪問),當我們EndXXX後,並不會馬上關閉這個句柄,而是等待垃圾收集器來關閉,這時候我們最後在調用EndXXX完成後,顯示的關閉這個句柄.
說到這裡,我們基本上把異步方法都解釋一遍,下面我們來看看重構的異步對象,我們也可以細細體會異步對象的內部執行代碼..下面Code3.1/3.2/3.3代碼來自Jeffery Richard大師的Power Threading類庫,具體可查看http://msdn.microsoft.com/en-us/magazine/cc163467.aspx
重構的異步對象
1步,構造一個內部無參的AsyncResultNoResult對象,繼承IAsyncResult接口(保留原創的注釋)
Code3.1
1 internal class AsyncResultNoResult : IAsyncResult 2 { 3 // Fields set at construction which never change while 4 // operation is pending 5 private readonly AsyncCallback m_AsyncCallback; 6 private readonly Object m_AsyncState; 7 8 // Fields set at construction which do change after 9 // operation completes 10 private const Int32 c_StatePending = 0; 11 private const Int32 c_StateCompletedSynchronously = 1; 12 private const Int32 c_StateCompletedAsynchronously = 2; 13 private Int32 m_CompletedState = c_StatePending; 14 15 // Field that may or may not get set depending on usage 16 private ManualResetEvent m_AsyncWaitHandle; 17 18 // Fields set when operation completes 19 private Exception m_exception; 20 21 public AsyncResultNoResult(AsyncCallback asyncCallback, Object state) 22 { 23 m_AsyncCallback = asyncCallback; 24 m_AsyncState = state; 25 } 26 27 public void SetAsCompleted( 28 Exception exception, Boolean completedSynchronously) 29 { 30 // Passing null for exception means no error occurred.31 // This is the common case 32 m_exception = exception; 33 34 // The m_CompletedState field MUST be set prior calling the callback 35 Int32 prevState = Interlocked.Exchange(ref m_CompletedState, 36 completedSynchronously ? c_StateCompletedSynchronously : 37 c_StateCompletedAsynchronously); 38 if (prevState != c_StatePending) 39 throw new InvalidOperationException( 40 "You can set a result only once"); 41 42 // If the event exists, set it 43 if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set(); 44 45 // If a callback method was set, call it 46 if (m_AsyncCallback != null) m_AsyncCallback(this); 47 } 48 49 public void EndInvoke() 50 { 51 // This method assumes that only 1 thread calls EndInvoke 52 // for this object 53 if (!IsCompleted) 54 { 55 // If the operation isn't done, wait for it 56 AsyncWaitHandle.WaitOne(); 57 AsyncWaitHandle.Close(); 58 m_AsyncWaitHandle = null; // Allow early GC 59 } 60 61 // Operation is done: if an exception occured, throw it 62 if (m_exception != null) throw m_exception; 63 } 64 65 Implementation of IAsyncResult#region Implementation of IAsyncResult 66 public Object AsyncState { get { return m_AsyncState; } } 67 68 public Boolean CompletedSynchronously 69 { 70 get 71 { 72 return Thread.VolatileRead(ref m_CompletedState) == 73 c_StateCompletedSynchronously; 74 } 75 } 76 77 public WaitHandle AsyncWaitHandle 78 { 79 get 80 { 81 if (m_AsyncWaitHandle == null) 82 { 83 Boolean done = IsCompleted; 84 ManualResetEvent mre = new ManualResetEvent(done); 85 if (Interlocked.CompareExchange(ref m_AsyncWaitHandle, 86 mre, null) != null) 87 { 88 // Another thread created this object's event; dispose 89 // the event we just created 90 mre.Close(); 91 } 92 else 93 { 94 if (!done && IsCompleted) 95 { 96 // If the operation wasn't done when we created 97 // the event but now it is done, set the event 98 m_AsyncWaitHandle.Set(); 99 } 100 } 101 } 102 return m_AsyncWaitHandle; 103 } 104 } 105 106 public Boolean IsCompleted 107 { 108 get 109 { 110 return Thread.VolatileRead(ref m_CompletedState) != 111 c_StatePending; 112 } 113 } 114 #endregion 115 }
2步,繼承AsyncResultNoResult對象,並且為他提供返回值和泛型的訪問
Code3.2
1internal class AsyncResult<TResult> : AsyncResultNoResult 2 { 3 // Field set when operation completes 4 private TResult m_result = default(TResult); 5 6 public AsyncResult(AsyncCallback asyncCallback, Object state) : 7 base(asyncCallback, state) { } 8 9 public void SetAsCompleted(TResult result, 10 Boolean completedSynchronously) 11 { 12 // Save the asynchronous operation's result 13 m_result = result; 14 15 // Tell the base class that the operation completed 16 // sucessfully (no exception) 17 base.SetAsCompleted(null, completedSynchronously); 18 } 19 20 new public TResult EndInvoke() 21 { 22 base.EndInvoke(); // Wait until operation has completed 23 return m_result; // Return the result (if above didn't throw) 24 } 25 }
3步,模擬長時間的異步工作
Code3.3
1internal sealed class LongTask 2 { 3 private Int32 m_ms; // Milliseconds; 4 5 public LongTask(Int32 seconds) 6 { 7 m_ms = seconds * 1000; 8 } 9 10 // Synchronous version of time-consuming method 11 public DateTime DoTask() 12 { 13 Thread.Sleep(m_ms); // Simulate time-consuming task 14 return DateTime.Now; // Indicate when task completed 15 } 16 17 // Asynchronous version of time-consuming method (Begin part) 18 public IAsyncResult BeginDoTask(AsyncCallback callback, Object state) 19 { 20 // Create IAsyncResult object identifying the 21 // asynchronous operation 22 AsyncResult<DateTime> ar = new AsyncResult<DateTime>( 23 callback, state); 24 25 // Use a thread pool thread to perform the operation 26 ThreadPool.QueueUserWorkItem(DoTaskHelper, ar); 27 28 return ar; // Return the IAsyncResult to the caller 29 } 30 31 // Asynchronous version of time-consuming method (End part) 32 public DateTime EndDoTask(IAsyncResult asyncResult) 33 { 34 // We know that the IAsyncResult is really an 35 // AsyncResult<DateTime> object 36 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult; 37 38 // Wait for operation to complete, then return result or 39 // throw exception 40 return ar.EndInvoke(); 41 } 42 43 // Asynchronous version of time-consuming method (private part 44 // to set completion result/exception) 45 private void DoTaskHelper(Object asyncResult) 46 { 47 // We know that it's really an AsyncResult<DateTime> object 48 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult; 49 try 50 { 51 // Perform the operation; if sucessful set the result 52 DateTime dt = DoTask(); 53 ar.SetAsCompleted(dt, false); 54 } 55 catch (Exception e) 56 { 57 // If operation fails, set the exception 58 ar.SetAsCompleted(e, false); 59 } 60 } 61 }
來自Jeffrey Richter大師更多更詳細的異步操作方法, 請查看http://www.wintellect.com/PowerThreading.aspx,對於一些朋友可能看不懂Code3.1-3.3代碼(其實沒什麼所謂的),因為涉及到過多的線程知識,這裡出於讓你獲得更多的更深層次的(異步)認識為目的,才提供上面代碼.
下一篇章中,我們來看看微軟提供有異步調用的類是如何調用的,並從中我會給出些真實應用環境中的一些小技巧,讓你編寫的代碼更健壯更完善.
本文配套源碼