begininvoke用法,invoke方法使用

  begininvoke用法,invoke方法使用

  委托无一例外地用于Invoke或BeginInvoke的使用中。关于授权的本质,请参考我的另一篇文章:

  1.为什么Control类提供Invoke和BeginInvoke机制?

  这个问题的主要原因dotnet程序员都很清楚,所以我会花点时间再把它记录在自己的日志里,以便日后提醒自己。

  1.windows程序消息机制

  Windows GUI程序基于消息机制,由一个主线程维护一个消息泵。这个消息泵使windows程序保持活动状态。

  Windows GUI程序的消息循环

  Windows程序有一个消息队列,窗体上的所有消息都是这个队列中消息的主要来源。这里的while循环使用的是GetMessage()方法,这是一种阻塞方法,即当队列为空时,该方法将被阻塞,从而while循环停止移动,防止一个程序无故耗尽cpu,导致其他程序难以得到响应。当然,在一些需要最大cpu移动的程序中也可以使用其他方法。比如在一些3d游戏或者即时战略游戏中,一般会使用PeekMessage()方法,不会被windows屏蔽,从而保证整个游戏的流畅度和高帧率。

  这个主线程维护整个窗体及其子控件。当它获得消息时,它将调用DispatchMessage方法来调度消息,这将导致调用窗体上的窗口过程。在这个过程中,当然还有表单数据更新代码和程序员提供的其他代码。

  2.网络中的信息流通

  公共静态void Main(string[] args)

  {

  Form f=new Form();

  申请。运行(f);

  }

  点形式程序封装了上面的while循环,由应用程序启动。运行方法。

  3.在线程外操作GUI控件的问题

  如果从另一个线程操作windows窗体上的控件,它将与主线程竞争,从而导致不可预知的结果甚至死锁。所以windows GUI编程中有一个规则,就是一个控件的数据只能由创建该控件的线程来操作,否则可能会产生意想不到的结果。

  因此,在dotnet中,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,并提供了Invoke和BeginInvoke方法,为其他线程提供了更新GUI界面控件的机制。

  公共接口是SynchronizeInvoke

  {

  [主机保护(安全操作。LinkDemand,Synchronization=true,ExternalThreading=true)]

  IAsyncResult BeginInvoke(委托方法,object[]args);

  object EndInvoke(IAsyncResult结果);

  对象调用(委托方法,object[]args);

  bool invoker需要{ get}

  }

  }

  如果从线程外部操作windows窗体控件,则需要使用Invoke或BeginInvoke方法通过委托将调用封送到控件所属的线程。

  二、消息机制——线程间和进程间的通信机制

  1.窗口消息发送

  Windows消息机制是Windows平台上的线程或进程间通信机制之一。Windows消息值实际上是一个定义好的数据结构,最重要的是消息的类型,是一个整数。然后是消息的参数。消息的参数可以代表很多东西。

  Windows提供了一些API来向线程的消息队列发送消息。因此,一个线程可以向另一个线程的消息队列发送消息,告诉对方该做什么,从而完成线程间的通信。一些API需要一个窗口句柄来发送消息。这个函数可以将消息发送到指定窗口的主线程消息队列中。而另一些可以直接通过线程句柄向线程的消息队列发送消息。

  通过消息机制进行通信

  SendMessage是一个windows api,用于向窗口的消息队列发送消息。此方法是一种阻塞方法,即操作系统将确保消息实际发送到目标消息队列,并且在消息处理完毕之前,函数不会返回。在返回之前,呼叫者将被暂时阻止。

  PostMessage也是一个api函数,用于向窗口消息队列发送消息,但是这个方法是非阻塞的。也就是说,不管消息是否实际发送到目的地,它都会立即返回,也就是说,调用方不会被阻塞。

  2、调用和开始调用

  调用或开始调用

  Invoke或BeginInvoke方法都需要委托对象作为参数。委托类似于回调函数的地址,所以调用者可以通过这两种方法把要调用的函数的地址封装到接口线程中。如果这些方法包含更改控件状态的代码,那么接口线程将最终执行该方法,从而避免竞争条件和意外问题。如果其他线程直接操作接口线程所属的控件,将会出现争用情况和不可预知的结果。

  使用Invoke封送委托方法类似于使用SendMessage方法向接口线程发送消息,这是一种同步方法。也就是说,Invoke方法直到执行了Invoke封送的方法才会返回,所以调用方线程会被阻塞。

  用BeginInvoke方法封送委托方法类似于用PostMessage进行通信,后者是一种异步方法。也就是说,该方法将在封送处理后立即返回,并且调用方的线程不会在不等待委托方法执行的情况下被阻塞。但是,调用方也可以使用EndInvoke方法或其他类似的WaitHandle机制来等待异步操作的完成。

  但在内部实现中,Invoke和BeginInvoke都使用了PostMessage方法,从而避免了SendMessage带来的问题。Invoke方法的同步阻塞由WaitHandle机制完成。

  3、使用场合。

  如果您的后台线程在更新UI控件的状态后不需要等待,而是继续处理它,那么您应该使用BeginInvoke进行异步处理。

  如果您的后台线程需要操作UI控件,并且需要等到操作完成后才能继续,那么您应该使用Invoke。否则,当后台线程和主段线程共享一些状态数据时,如果不同步调用,而是继续分开执行,可能会导致执行顺序出现问题。虽然不会出现死锁,但会出现意外的显示结果或数据处理错误。

  可以看到ISynchronizeInvoke有一个属性InvokeRequired。此属性用于确定在编程时访问UI控件时,对象是否需要使用Invoke或BeginInvoke进行封送处理。如果不需要,可以直接更新。当调用者对象和UI对象属于同一个线程时,此属性返回false。在后面的代码分析中我们可以看到,Control类实现这个属性是为了判断调用者和控件是否属于同一个线程。

  第三,放权。异步调用

  通过委托异步调用同步方法也是提供的异步调用机制之一。网。然而,代表。BeginInvoke方法从ThreadPool中取出一个线程来执行该方法,从而达到异步执行的效果。也就是说,如果以这种方式提交多个异步委托,则无法保证这些调用的顺序。而且由于线程池中的线程是用来完成任务的,频繁使用会影响系统的性能。

  委派。BeginInvoke还意味着通过异步机制将委托方法封送到其他线程来执行方法。在封送处理之后,调用方线程可以继续其工作。但是此方法封送到的最终线程是运行库从ThreadPool中选择的线程。

  这里需要纠正一个误区,就是控件类上的异步调用BeginInvoke并没有开辟一个新的线程来完成委托的任务,而是让接口控件的线程来完成委托的任务。看来异步操作就是开一个新线程的说法不一定准确。

  第四,用Reflector查看一些相关代码。

  1、控制。开始创新和控制调用public IAsyncResult BeginInvoke(Delegate method,params object[]args){ using(new MultithreadSafeCallScope()){ return(IAsyncResult)this .FindMarshalingControl().MarshaledInvoke(this,method,args,false);} }公共对象调用(代表方法,params object[]args){ using(new MultithreadSafeCallScope()){ return this .FindMarshalingControl().MarshaledInvoke(this,method,args,true);}}

  这里的findhamarshalingcontrol方法通过一个循环向上回溯,从当前控件开始回溯父控件,直到找到最顶级的父控件,用它作为封送对象。例如,我们调用窗体上一个进度条的引起方法封送委托,但是实际上会回溯到主窗体,通过这个控件对象来封送委托。因为主窗体是主线程消息队列相关的,发送给主窗体的消息才能发送到界面主线程消息队列。

  我们可以看到引起和异步调用方法使用了同样的实现,只是马歇尔丁沃克方法的最后一个参数值不一样。

  2、MarshaledInvokeprivate对象马歇尔丁沃克(控件调用方,委托方法,object[] args,bool synchronous){ int num;如果(!这个IsHandleCreated){ throw new InvalidOperationException(SR . GetString( ErrorNoMarshalingThread );} if((ActiveXImpl)这个属性。GetObject(proactiveximpl))!=null) { IntSecurity .非托管代码。demand();} bool flag=false if((SafeNativeMethods .GetWindowThreadProcessId(new HandleRef(this,this .Handle),out num)==SafeNativeMethods .GetCurrentThreadId())synchronous){ flag=true;}执行上下文执行上下文=null如果(!flag){执行上下文=执行上下文.捕获();} ThreadMethodEntry entry=new ThreadMethodEntry(调用者、方法、参数、同步、执行上下文);锁定(这个){ if(这个。threadcallbacklist==null){ this。threadcallbacklist=new Queue();} }锁定(这个。threadcallbacklist){ if(threadCallbackMessage==0){ threadCallbackMessage=SafeNativeMethods .注册窗口消息(应用程序WindowMessagesVersion _ ThreadCallbackMessage );}这个。threadcallbacklist。入队(条目);} if (flag) { this .InvokeMarshaledCallbacks();} else { //终于找到你了、邮件后非防御方法.邮件(新HandleRef(这个,这个Handle)、threadCallbackMessage、IntPtr .零,IntPtr .零);}如果(!同步)//如果是异步,那么马上返回吧{返回条目;}如果(!入口. IsCompleted) //同步调用没结束,阻塞起来等待吧{这个WaitForWaitHandle(条目AsyncWaitHandle);} if (entry.exception!=null){ throw entry。例外;}返回entry.retVal}

  怎么样,我们终于看到邮件了吧?通过窗子消息机制实现了封送。而需要封送的委托方法作为消息的参数进行了传递。关于其它的代码这里不作进一步解释。

  3、invoker必需的public bool invoker必需的{ get { using(new MultithreadSafeCallScope()){ HandleRef re F2;整数如果(这个IsHandleCreated){ re F2=new HandleRef(this,this .手柄);}否则{控制包装=这个.findhamarshalingcontrol();如果(!包装纸IsHandleCreated) {返回false } re F2=new HandleRef(wrapper,wrapper .手柄);} int windowThreadProcessId=SafeNativeMethods .GetWindowThreadProcessId(ref2,out num);int current threadid=SafeNativeMethods .GetCurrentThreadId();return (windowThreadProcessId!=currentThreadId);} }

  }

  终于看到了,这是在判断窗子窗体线程和当前的调用者线程是否是同一个,如果是同一个就没有必要封送了,直接访问这个图像使用者界面控件吧。否则,就不要那么直接表白了,就需要引起或者异步调用做媒了。

begininvoke用法,invoke方法使用