IAsyncResult大伙都用的多吧,在异步编程的时候,我们通常根据它的IsCompleted属性来判断异步操作是否已完成。不过该接口还有另外一个属性CompletedSynchronously,MSDN的解释一如既往的简单(查阅MSDN的时候,往往能看懂它的文字,不理解它的意思,有同感的朋友举手。):获取异步操作是否同步完成的指示。
异步操作,同步完成,到底啥意思呢。假如我是IAsyncResult接口的实现者,我该如何设置何时设置CompletedSynchronously的值呢?假如我是IAsyncResult接口实现类的使用者,CompletedSynchronously对于我来说又有什么用呢?以下是我个人看法,若有错误,欢迎拍砖。
假设我们自定义的类需要实现某个耗费时间可能长可能短的功能,时间长短并不受代码控制,比如获取某个资源进行操作,请求资源时经常需要排队,偶尔又能马上取得。
1 public class CompletedSynchronouslyShow 2 { 3 int _blockInterval = new Random().Next(0, 10000); 4 5 ///6 /// 为了更好地说明问题,我们将该方法定义成有返回值 7 /// 8 ///执行是否成功 9 public bool DoSth()10 {11 //模拟真实情形,阻塞不定时间12 System.Threading.Thread.Sleep(_blockInterval);13 14 //其它操作15 return true;16 }17 }
为了不阻塞当前线程,我们需要使用异步方式比如new Thread(Show.DoSth).Start()来开启新线程调用DoSth(Show为CompletedSynchronouslyShow类型的对象)。众所周知,虽然硬件性能步步高升,但开启并维护一个新线程对系统来说还是一件体力活。如果阻塞的时间并不久,我们希望仍然在当前线程执行DoSth方法。由于阻塞时间只能在DoSth方法内部做判断,因此上述愿望的实现不能期望调用方,不论异步还是同步,调用方调用的只能是同一个方法。既然是同一个方法,那么方法的返回值不能是原先的返回值(这里是布尔型),一般情况下IAsyncResult是最合适的(我们也可以自定义返回类型,只要能满足要求)。而IAsyncResult接口的CompletedSynchronously属性正是指示方法执行完成时所在的线程是否就是调用线程。
下面是修改后的代码:
1 public class CompletedSynchronouslyShow 2 { 3 int _blockInterval = new Random().Next(0, 10000); 4 5 ///6 /// 为了更好地说明问题,我们将该方法定义成有返回值 7 /// 8 ///执行是否成功 9 private bool DoSth()10 {11 //模拟真实情形,阻塞不定时间12 System.Threading.Thread.Sleep(_blockInterval);13 14 //其它操作15 return true;16 }17 18 //客户端调用该方法而不是DoSth方法19 public IAsyncResult BeginDoSth(AsyncCallback callback = null, object state = null)20 {21 return new AsyncResult(_blockInterval, new Func(DoSth), callback, state);22 }23 }24 25 public class AsyncResult : IAsyncResult26 {27 private Func _doSth;28 private AsyncCallback _callback;29 30 public bool Result { get; private set; }31 32 private bool _completedSynchronously;33 public bool CompletedSynchronously34 {35 get { return _completedSynchronously; }36 }37 38 private bool _isCompleted;39 public bool IsCompleted40 {41 get { return _isCompleted; }42 }43 44 private object _state;45 public object AsyncState46 {47 get { return _state; }48 }49 50 public AsyncResult(int blockInterval, Func doSth, AsyncCallback callback, object state)51 {52 _doSth = doSth;53 _state = state;54 _callback = callback;55 //小于3秒直接返回值56 if (blockInterval < 3000)57 {58 Result = doSth();59 //_completedSynchronously设为true表示同步执行60 this._completedSynchronously = this._isCompleted = true;61 if (_callback != null)62 {63 _callback(this);64 }65 }66 else67 {68 doSth.BeginInvoke(new AsyncCallback(SetResult), null);69 70 }71 }72 73 private void SetResult(IAsyncResult ar)74 {75 Result = _doSth.EndInvoke(ar);76 if (_callback != null)77 {78 _callback(this);79 }this._isCompleted = true;80 }81 }
恩,少年们于是又不淡定了:这不是玩我呢吧,这都啥玩意。上述代码核心在于实现了IAsyncResult接口,通过它我们才能展开后续工作。这里说明CompletedSynchronously属性的意义:BeginDoSth方法给人一种异步的错觉,但它同时兼有异步和同步的特性,由CompletedSynchronously属性区分。它还体现了一种编程模式,该模式在.Net框架中很多地方都在使用,比如WCF输出信道接口IOutputChannel:
1 public interface IOutputChannel2 {3 IAsyncResult BeginSend(Message message, AsyncCallback callback, object state);4 void EndSend(IAsyncResult result);5 }
很多具有异步操作的类型都有类似BeginXX和EndXX成对出现的方法,参数也大同小异。EndXX的作用同委托的EndInvoke方法一样,可供回调函数使用,当我们需要获取操作的返回值时,在回调函数中一般要调用该方法。
作为IAsyncResult接口实现方,CompletedSynchronously一般设为false即可,一个原因是判断何时采用同步方式合适采用异步方式(如例子中获取阻塞时间)在大多数情况下并不容易。我觉得在资源请求、Stream操作等方面可以考虑这个问题。网络通信中,Socket.BeginSendTo方法就可能返回CompletedSynchronously为true的对象,它是按照什么来作为设置的依据,我反射了代码,也查不出一个所以然,我只好猜测它是根据发送字节总数和单次最大发送字节数比较结果选择是否发送方式(同步or异步)。
有时候CompletedSynchronously属性似乎并不像它本身意思,更多的是“数据是否完整”(多次异步操作数据流会引出这个概念)。我是看一文得出这个结论的,该文说道web响应流BeginRead返回对象若CompletedSynchronously为true,则说明web响应流已经全部接收完毕,此时应该使用Read同步方法。我觉得该说法有误,既然BeginRead返回的CompletedSynchronously已经为true,说明BeginRead执行在当前线程,那么后续执行也应该是当前线程,从这么方面来说它和Read方法是一样的,可能在其它方面会有损耗(比如构造IAsyncResult对象)。另一方面,若真如该文所说,那么即使某次BeginRead读取的字节已经是接收到本地的,但由于数据并未全部接收完,所以该次BeginRead返回的CompletedSynchronously仍为false。不论如何,真相只有一个,留待以后验证,有知道的朋友希望不吝赐教,感激不尽。
转载请注明本文出处: