Control.Invoke和Control.BeginInvoke
Control.Invoke()是同步方法,它会往Control所处的线程(UI主线程)消息队列中加一条消息,请求执行某个委托,在该委托方法(可能多个)执行完毕后,Control.Invoke()得以返回,继续执行下一行代码。当委托的InvocationList元素有多个时,即有多个方法需要执行时,Control.Invoke()返回值是最后一个执行的方法的返回值。
Control.BeginInvoke是异步方法,它往Control所处的线程(UI主线程)消息队列中加一条消息,请求执行某个委托,然后立刻返回。至于请求的方法何时开始执行,它不关心。但是请求的方法执行的结果,它必然要关心,所以Control.BeginInvoke()有一个IAsyncResult的返回值,这个IAsyncResult可以看作委托的凭据。Control.BeginInvoke()调用后,可以在任意时间后调用Control.EndInvoke(IAsyncResult)来获得该凭据对应的执行结果。如果尚未执行完,则一直在那等,直到执行完成为止。EndInvoke的返回值是一个object,即为委托方法的返回值(同样地,如果有多个委托方法,则为最后一个方法的返回值)。
值得注意的是,EndInvoke只能被调用一次,否则会引发异常。
在调用Control.Invoke/BeginInvoke之前,可以先使用Control.InvokeRequired检测是否调用者当前线程跟Control创建线程不同,如果不同,则表示必须使用Invoke来调用对Control进行操作的方法,否则可以直接调用。
以下代码可以用来演示Control.InvokeRequired的使用:
private delegate int AddDel(int value);
private AddDel addDel = (value) =>
{
return ++value;
};
private void button1_Click(object sender, EventArgs e)
{
Button button = sender as Button;
if(button.InvokeRequired) //一定为false
{
//blabla
}
//请求使用线程池中的线程来执行workThreadMathod
ThreadPool.QueueUserWorkItem(workThreadMathod, sender);
}
private void workThreadMathod(object obj)
{
Button button = obj as Button;
if (button.InvokeRequired) //一定为 true
{
//blabla
}
}
Control.BeginInvoke返回的IAsyncResult不能强转为AsyncResult,AsyncResult类只用于Delegate的BeginInvoke。
IAsyncResult. AsyncWaitHandle.WaitOne()如果被调用,则一直会等到委托方法执行完毕后返回,这应该就是EndInvoke的内部实现。但是它也开放出来给用户使用了,因为有些场合,既需要等候委托方法的完成,又需要在取得委托方法的返回值之前(调用EndInvoke)干点别的什么事,那么就需要手动调用WaitOne()了。当然,如果没有这种变态需求,直接调用EndInvoke即可。IAsyncResult. AsyncWaitHandle.WaitOne()使用后,需要调用Close()来关闭句柄。
有一点需要注意的是,如果Control.BeginInvoke是在创建Control的线程(主线程)中被调用,那么一定不能使用IAsyncResult. AsyncWaitHandle.WaitOne(),因为这样会造成死锁,调用处在无限等待,委托方法又得不到执行,线程就一直被挂起了。
Delegate.Invoke 和 Delegate.BeginInvoke:
跟Control.Invoke、Control.BeginInvoke类似的,前者是同步方法,后者是异步方法。
Delegate.Invoke使用起来就跟直接调用方法类似,参数就是委托的参数类型。
也可以使用完全跟方法调用一样的形式,即加括号()发方式来调用,内部实现上依旧是调用的Invoke方法。
Delegate.Invoke除了是同步的以外,还有一个显著特征是,委托方法执行在调用处同一个线程中。
Delegate.BeginInvoke则不同,它除了是异步的以外,还有个很重要的特征,它从线程池中抓取一个空闲线程,来执行委托方法。
看一下Delegate.BeginInvoke的签名:
public class xxxDelegate : MulticastDelegate
{
public IAsyncResult BeginInvoke(...,AsyncCallback cb, object @object);
}
...处是委托方法的参数列表,cb是一个AsyncCallback实例,AsyncCallback的定义如下:
public delegate void AsyncCallback(IAsyncResult ar);
@object是调用者自己传入的对象,到时候在AsyncCallback委托的方法中可以获取到。
下面结合实例讲一下BeginInvoke的使用流程:
private delegate int AddDel(int value);
private AddDel addDel = (value) =>
{
return ++value;
};
private void button1_Click(object sender, EventArgs e)
{
IAsyncResult iar = addDel.BeginInvoke(33, (v) => //1
{
AsyncResult ar = v as AsyncResult; //2
AddDel del = ar.AsyncDelegate as AddDel; //3
string inputObj = ar.AsyncState as string; //4
if (!ar.EndInvokeCalled) //5
{
int result = (int)del.EndInvoke(ar); //6
}
},@"abc"); //7
int result1 = addDel.EndInvoke(iar); //8
}
为了方便序数,以上事例各行都编了号。
第1行,请求线程池安排一个空闲线程来执行addDel的委托方法,参数为33
第2~6行,使用一个lambda表达式作为BeginInvoke的AsyncCallback参数,表示说在addDel的委托方法返回后,需要执行该AsyncCallback实例的委托方法,即执行花括号内的代码块。注意,这个代码块运行于addDel委托方法同一个线程中。
第2行,将IAsyncResult类型的v强转成AsyncResult对象,这个跟Control.BeginInvoke的返回值不同,只有Delegate.BeginInvoke的返回值才是一个AsyncResult实例。
同时,ar和第1行的iar实际上是同一个引用。
第3行,从ar中拿到委托实例,del和addDel实际上是同一个引用。
第4行,从ar中拿到传入的对象,即第7行的@”abc”。
第5行,判断EndInvoke是否已经调用过了。因为EndInvoke只能调用一次,所以提供这个方法来提前判断。
第6行,调用EndInvoke来获取addDel委托方法的返回值。
第8行,这行代码所处的线程和2~6行所处的线程是不同的,因此实际上是无法知道到底第8行跟第6行哪一个EndInvoke会真正执行,而且,极端的情况下,如果第6行得到执行的话,那第8行是会引发异常的,同样,如果第8行执行完后,第6行所处的线程得到调度,那第6行也会引发异常。第5行的EndInvokeCalled理论上在这里毫无作用。
以上只是演示了Delegate.BeginInvoke的使用。实际的编码过程中,应该要避免在Delegate.BeginInvoke的调用线程以及AsyncCallback委托方法中同时进行EndInvoke的处理逻辑。
至于到底把后续逻辑放在哪一块来执行,就得看需求而定了,如果是耗时的后台操作,那么放在AsyncCallback的委托方法中比较好;如果addDel.BeginInvoke调用处本身也是一个工作线程,那似乎放到第8行以后来处理会比较好,毕竟可以不用切换上下文环境。
Delegate.DynamicInvoke
跟Delegate.Invoke类似,同步,且同线程,唯一不同的是,是采用后期绑定的方式来调用委托方法。
假如一个Delegate(注意D是大小的,表示类实例),必须要到运行时动态来确定其方法签名(即该Delegate实例是通过Delegate.CreateDelegate()动态创建的),那就要用到DynamicInvoke()。查看Delegate类的结构,发现DynamicInvoke是一个public方法,同时还有protected virtual object DynamicInvokeImpl(object[] args);这么一个虚方法。猜测通过delegate关键字定义的类,应该是重写了这个虚方法。
那么如果对一个通过Delegate.CreateDelegate()动态创建的Delegate实例调用Invoke会怎样?答案是没有这回事,因为Delegate根本就没有Invoke方法。
对于通过delegate关键字定义的Delegate类,由于编译期间已经确定了其方法签名,那么用Invoke或者DynamicInvoke,效果是一样的。当然,由于DynamicInvoke在编译时不检查方法签名是否匹配,如果在运行时发现不匹配的话,会引发异常。Invoke由于在编译期间就已经确保了签名匹配,所以运行期间不会引发参数不匹配异常。
WP7中的BeginInvoke
目前版本的wp7中,Control.Invoke以及Control.BeginInvoke都不见了踪影。
Delegate.Invoke可用,而Delegate.BeginInvoke在运行时却会抛异常。所以很令人沮丧。
目前我找到的唯一可用的BeginInvoke,位于System.Windows.Threading.Dispatcher.BeginInvoke。
在Silverlight 4.0 for WP中,任何一个DependencyObject都有一个Dispatcher属性,可以获取到代表当前主线程的Dispatcher对象,调用其BeginInvoke即可呼叫主线程来执行提供的委托。
不过其返回值DispatcherOperation对象是没有任何实现的,简直坑爹呀。