上次在调试串口的时候,遇到了Invoke卡死的bug,第一次接触到了BeginInvoke,于是就找了找这两者的区别。
Invoke and BeginInvoke这篇文章介绍了windows程序消息机制,有兴趣可以看一下。
总结下来就是这么回事:
使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。
但是在内部实现上,Invoke和BeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。
如果用代码来看的话,下面两种写法的效果是完全相同的。
this.Invoke(new Action(() =>
{
textBox1.AppendText(s);
}));
IAsyncResult asyncResult = this.BeginInvoke(new Action(() =>
{
textBox1.AppendText(s);
}));
while (!asyncResult.AsyncWaitHandle.WaitOne())
{
}
this.EndInvoke(asyncResult);
IAsyncResult asyncResult = this.BeginInvoke(new Action(() =>
{
textBox1.AppendText(s);
}));
while (!asyncResult.IsCompleted)
{
}
this.EndInvoke(asyncResult);
假如代理通知的事情一直没有完成,使用Invoke就会一直阻塞再这里。
考虑到asyncResult.IsCompleted不具有阻塞性,即cpu会一直执行类似while(true)的语句,程序是真的退不出去。而asyncResult.AsyncWaitHandle.WaitOne()还具有多个重载:
//
// 摘要:
// 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
//
// 返回结果:
// 如果当前实例收到信号,则为 true。 如果当前实例永远收不到信号,则 System.Threading.WaitHandle.WaitOne(System.Int32,System.Boolean)
// 永不返回。
//
// 异常:
// System.ObjectDisposedException:
// 当前实例已被释放。
//
// System.Threading.AbandonedMutexException:
// 线程退出时未释放互斥体,等待过程已终止。 在 Windows 98 或 Windows Millennium Edition 中不引发此异常。
//
// System.InvalidOperationException:
// 当前实例是另一个应用程序域中的 System.Threading.WaitHandle 的透明代理。
public virtual bool WaitOne();
//
// 摘要:
// 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔。
//
// 参数:
// millisecondsTimeout:
// 等待的毫秒数,或为 System.Threading.Timeout.Infinite (-1),表示无限期等待。
//
// 返回结果:
// 如果当前实例收到信号,则为 true;否则为 false。
//
// 异常:
// System.ObjectDisposedException:
// 当前实例已被释放。
//
// System.ArgumentOutOfRangeException:
// millisecondsTimeout 是一个非 -1 的负数,而 -1 表示无限期超时。
//
// System.Threading.AbandonedMutexException:
// 线程退出时未释放互斥体,等待过程已终止。 在 Windows 98 或 Windows Millennium Edition 中不引发此异常。
//
// System.InvalidOperationException:
// 当前实例是另一个应用程序域中的 System.Threading.WaitHandle 的透明代理。
public virtual bool WaitOne(int millisecondsTimeout);
参考c#线程之异步委托begininvoke、invoke、AsyncWaitHandle.WaitOne 、异步回调和BeginInvoke和EndInvoke方法两篇博客,可以使用WaitOne(int millisecondsTimeout)
方法。
如果invoke没有返回,则阻塞500ms后,WaitOne()函数返回false,可以做异常处理。
如果500ms以内返回,正常结束Invoke。
IAsyncResult asyncResult = this.BeginInvoke(new Action(() =>
{
textBox1.AppendText(s);
}));
if (asyncResult.AsyncWaitHandle.WaitOne(500))
{
this.EndInvoke(asyncResult);
}
else
{
MessageBox.Show("返回超时");
}
通过一个委托来进行同步方法的异步调用,也是.net提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以获得异步执行效果的。也就是说,如果采用这种方式提交多个异步委托,那么这些调用的顺序无法得到保证。而且由于是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能造成影响。
Delegate.BeginInvoke也是讲一个委托方法封送到其它线程,从而通过异步机制执行一个方法。调用者线程则可以在完成封送以后去继续它的工作。但是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。
这里需要纠正一个误区,那就是Control类上的异步调用BeginInvoke并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操作就是开辟新线程的说法不一定准确。
实际也测试过,在Action中加Thread.Sleep,会让UI线程1s响应一次,因为这里的BeginInvoke是Control.Invoke,并不是单独开一个线程去执行的Action。