在上一篇文章(), 我们已经简单介绍了异步的调用方式, 下面我们来看看异步的核心.
异步的核心: IAsyncResult
Asynchronous Programming Model
整个异步调用过程中都是围绕IAsyncResult来进行的,大家可以看看上篇文章的例子,BeginXXX 返回这个对象,EndXXX接收这个对象来结束当前异步对象,下面我们来看看IAsyncResult 接口成员/和实现此接口的AsyncResult类成员(其中有些在上篇中已经涉及到)
IAsyncResult接口
1 public interface IAsyncResult 2 { 3 WaitHandle AsyncWaitHandle { get; } //阻塞一个线程,直到一个或多个同步对象接收到信号4 Boolean IsCompleted { get; } //判读当前异步是否完成5 Object AsyncState { get; } //获取额外的参数值,请看上一篇文章的Code 4.36 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/512 }
注意:基本上都是只读属性
下面我们来看看异步的执行顺序,并回顾下 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);1213 DoAntherJob();14 Console.ReadLine(); // 让黑屏等待,不会直接关闭..15 }1617 //开始其他工作.18 static void DoAntherJob()19 { 20 Thread.Sleep(1000);//需要1秒才能完成这个工作,注121 Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);22 }2324 static void OnSalaryCallback(IAsyncResult asyncResult)25 { 26 //通过AsyncState 获取额外的参数.27 decimal para = (int)asyncResult.AsyncState;2829 //通过AsyncDelegate 获取原始的委托对象30 AsyncResult obj = (AsyncResult)asyncResult;31 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = (MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;3233 if (asyncResult.IsCompleted)// 判读是否已经调用完成34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);3536 decimal val = del.EndInvoke(asyncResult);3738 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秒,注249 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;1011 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);1718 //开始其他工作.19 DoAntherJob();20 Console.ReadLine(); // 让黑屏等待,不会直接关闭..21 }22 static void DoAntherJob()23 { 24 Thread.Sleep(1000);//需要1秒才能完成这个工作,注125 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;3233 if (asyncResult.IsCompleted)// 判读是否已经调用完成34 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);3536 decimal val = del.EndInvoke(asyncResult);37 Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId);38 }3940 static void OnHelloCallback(IAsyncResult asyncResult)41 { 42 //通过AsyncDelegate 获取原始的委托对象43 AsyncResult obj = (AsyncResult)asyncResult;44 MyThirdAsyncCode.AsyncTest.AsyncEventHandler del = (MyThirdAsyncCode.AsyncTest.AsyncEventHandler)obj.AsyncDelegate;4546 if (asyncResult.IsCompleted)// 判读是否已经调用完成47 Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);4849 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
1 static void OnSalaryCallback(IAsyncResult asyncResult) 2 { 3 //通过AsyncDelegate 获取原始的委托对象4 AsyncResult obj = (AsyncResult)asyncResult;5 MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = (MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;67 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类库,具体可查看
重构的异步对象
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 }105106 public Boolean IsCompleted107 { 108 get109 { 110 return Thread.VolatileRead(ref m_CompletedState) !=111 c_StatePending;112 }113 }114 #endregion115 }
2步,继承AsyncResultNoResult对象,并且为他提供返回值和泛型的访问
Code3.2
1 internal 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 result13 m_result = result;1415 // Tell the base class that the operation completed 16 // sucessfully (no exception)17 base.SetAsCompleted(null, completedSynchronously);18 }1920 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
1 internal sealed class LongTask 2 { 3 private Int32 m_ms; // Milliseconds; 4 5 public LongTask(Int32 seconds) 6 { 7 m_ms = seconds * 1000; 8 } 910 // Synchronous version of time-consuming method11 public DateTime DoTask()12 { 13 Thread.Sleep(m_ms); // Simulate time-consuming task14 return DateTime.Now; // Indicate when task completed15 }1617 // 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 operation22 AsyncResult<DateTime> ar = new AsyncResult<DateTime>(23 callback, state);2425 // Use a thread pool thread to perform the operation26 ThreadPool.QueueUserWorkItem(DoTaskHelper, ar);2728 return ar; // Return the IAsyncResult to the caller29 }3031 // 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> object36 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;3738 // Wait for operation to complete, then return result or 39 // throw exception40 return ar.EndInvoke();41 }4243 // 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> object48 AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;49 try50 { 51 // Perform the operation; if sucessful set the result52 DateTime dt = DoTask();53 ar.SetAsCompleted(dt, false);54 }55 catch (Exception e)56 { 57 // If operation fails, set the exception58 ar.SetAsCompleted(e, false);59 }60 }61 } 来自Jeffrey Richter大师更多更详细的异步操作方法, 请查看http://www.wintellect.com/PowerThreading.aspx,对于一些朋友可能看不懂Code3.1-3.3代码(其实没什么所谓的),因为涉及到过多的线程知识,这里出于让你获得更多的更深层次的(异步)认识为目的,才提供上面代码,在以后的文章会再次探讨.
下一篇章中,我们来看看微软提供有异步调用的类是如何调用的,并从中我会给出些真实应用环境中的一些小技巧,让你编写的代码更健壮更完善.
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,
2,(VS2008开发,.Net Framework 2.0(C Sharp)编写)
文章为原创,如果需要引用,请保留原地址. 有什么问题/错误的地方请联系 fox7805034 (at) hotmail.com