`
isiqi
  • 浏览: 16483321 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

介绍 Invoke and BeginInvoke

阅读更多

Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:.net事件的看法

一、为什么Control类提供了InvokeBeginInvoke机制?

关于这个问题的最主要的原因已经是dotnet程序员众所周知的,我在此费点笔墨再次记录到自己的日志,以便日后提醒一下自己。

1windows程序消息机制

Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵。这个消息泵让windows程序生生不息。

Windows GUI程序的消息循环

Windows程序有个消息队列,窗体上的所有消息是这个队列里面消息的最主要来源。这里的while循环使用了GetMessage()这个方法,这是个阻塞方法,也就是队列为空时方法就会被阻塞,从而这个while循环停止运动,这避免了一个程序把cpu无缘无故地耗尽,让其它程序难以得到响应。当然在某些需要cpu最大限度运动的程序里面就可以使用另外的方法,例如某些3d游戏或者及时战略游戏中,一般会使用PeekMessage()这个方法,它不会被windows阻塞,从而保证整个游戏的流畅和比较高的帧速。

这个主线程维护着整个窗体以及上面的子控件。当它得到一个消息,就会调用DispatchMessage方法派遣消息,这会引起对窗体上的窗口过程的调用。窗口过程里面当然是程序员提供的窗体数据更新代码和其它代码。

2dotnet里面的消息循环

public static void Main(string[] args)

{

Form f = new Form();

Application.Run(f);

}

Dotnet窗体程序封装了上述的while循环,这个循环就是通过Application.Run方法启动的。

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

如果从另外一个线程操作windows窗体上的控件,就会和主线程产生竞争,造成不可预料的结果,甚至死锁。因此windows GUI编程有一个规则,就是只能通过创建控件的线程来操作控件的数据,否则就可能产生不可预料的结果。

因此,dotnet里面,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了InvokeBeginInvoke方法来提供让其它线程更新GUI界面控件的机制。

public interface ISynchronizeInvoke

{

[HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]

IAsyncResult BeginInvoke(Delegate method, object[] args);

object EndInvoke(IAsyncResult result);

object Invoke(Delegate method, object[] args);

bool InvokeRequired { get; }

}

}

如果从线程外操作windows窗体控件,那么就需要使用Invoke或者BeginInvoke方法,通过一个委托把调用封送到控件所属的线程上执行。

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

1window消息发送

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

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

用消息机制通信

SendMessagewindows api,用来把一个消息发送到一个窗口的消息队列。这个方法是个阻塞方法,也就是操作系统会确保消息的确发送到目的消息队列,并且该消息被处理完毕以后,该函数才返回。返回之前,调用者将会被暂时阻塞。

PostMessage也是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会马上返回,而不管消息是否真的发送到目的地,也就是调用者不会被阻塞。

2Invoke and BeginInvoke

Invoke or BeginInvoke

Invoke或者BeginInvoke方法都需要一个委托对象作为参数。委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码,那么由于最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。如果其它线程直接操作界面线程所属的控件,那么将会产生竞争条件,造成不可预料的结果。

使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。

使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。

但是在内部实现上,InvokeBeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。

3、使用场合问题

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

如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。否则,在后台线程和主截面线程共享某些状态数据的情况下,如果不同步调用,而是各自继续执行的话,可能会造成执行序列上的问题,虽然不发生死锁,但是会出现不可预料的显示结果或者数据处理错误。

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

三、Delegate.BeginInvoke

通过一个委托来进行同步方法的异步调用,也是.net提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以获得异步执行效果的。也就是说,如果采用这种方式提交多个异步委托,那么这些调用的顺序无法得到保证。而且由于是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能造成影响。

Delegate.BeginInvoke也是讲一个委托方法封送到其它线程,从而通过异步机制执行一个方法。调用者线程则可以在完成封送以后去继续它的工作。但是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。

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

四、用Reflector察看一些相关代码

1Control.BeginInvoke and Control.Invoke

public IAsyncResult BeginInvoke(Delegate method, params object[] args)
{
 using (new MultithreadSafeCallScope())
 {
 return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);
 }
}
public object Invoke(Delegate method, params object[] args)
{
 using (new MultithreadSafeCallScope())
 {
 return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
 }
}

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

我们可以看到InvokeBeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样。

2MarshaledInvoke

private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
 int num;
 if (!this.IsHandleCreated)
 {
 throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread"));
 }
 if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null)
 {
 IntSecurity.UnmanagedCode.Demand();
 }
 bool flag = false;
 if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId()) && synchronous)
 {
 flag = true;
 }
 ExecutionContext executionContext = null;
 if (!flag)
 {
 executionContext = ExecutionContext.Capture();
 }
 ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
 lock (this)
 {
 if (this.threadCallbackList == null)
 {
 this.threadCallbackList = new Queue();
 }
 }
 lock (this.threadCallbackList)
 {
 if (threadCallbackMessage == 0)
 {
 threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
 }
 this.threadCallbackList.Enqueue(entry);
 }
 if (flag)
 {
 this.InvokeMarshaledCallbacks();
 }
 else
 {            //终于找到你了,PostMessage
 UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
 }
 if (!synchronous) //如果是异步,那么马上返回吧
 {
 return entry;
 }
 if (!entry.IsCompleted) //同步调用没结束,阻塞起来等待吧
 {
 this.WaitForWaitHandle(entry.AsyncWaitHandle);
 }
 if (entry.exception != null)
 {
 throw entry.exception;
 }
 return entry.retVal;
}

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

3InvokeRequired

public bool InvokeRequired
{
 get
 {
 using (new MultithreadSafeCallScope())
 {
 HandleRef ref2;
 int num;
 if (this.IsHandleCreated)
 {
 ref2 = new HandleRef(this, this.Handle);
 }
 else
 {
 Control wrapper = this.FindMarshalingControl();
 if (!wrapper.IsHandleCreated)
 {
 return false;
 }
 ref2 = new HandleRef(wrapper, wrapper.Handle);
 }
 int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
 int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
 return (windowThreadProcessId != currentThreadId);
 }
 }

}

终于看到了,这是在判断windows窗体线程和当前的调用者线程是否是同一个,如果是同一个就没有必要封送了,直接访问这个GUI控件吧。否则,就不要那么直接表白了,就需要Invoke或者BeginInvoke做媒了。

近日,被Control的Invoke和BeginInvoke搞的头大,就查了些相关的资料,整理如下。感谢这篇文章对我的理解Invoke和BeginInvoke的真正含义
(一)Control的Invoke和BeginInvoke
我们要基于以下认识:
(1)Control的Invoke和BeginInvoke与Delegate的Invoke和BeginInvoke是不同的。
(2)Control的Invoke和BeginInvoke的参数为delegate,委托的方法是在Control的线程上执行的,也就是我们平时所说的UI线程。

我们以代码(一)来看(Control的Invoke)
private delegate void InvokeDelegate();
private void InvokeMethod(){
//C代码段
}
private void butInvoke_Click(object sender, EventArgs e) {
//A代码段.......
this.Invoke(new InvokeDelegate(InvokeMethod));
//B代码段......
}
你觉得代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A------>C---------------->B
解释:(1)A在UI线程上执行完后,开始Invoke,Invoke是同步
(2)代码段B并不执行,而是立即在UI线程上执行InvokeMethod方法,即代码段C。
(3)InvokeMethod方法执行完后,代码段C才在UI线程上继续执行。

看看代码(二),Control的BeginInvoke
private delegate void BeginInvokeDelegate();
private void BeginInvokeMethod(){
//C代码段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
//A代码段.......
this.BeginInvoke(new BeginInvokeDelegate(BeginInvokeMethod));
//B代码段......
}

你觉得代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A----------->B--------------->C慎重,这个只做参考。。。。。,我也不肯定执行顺序,如果有哪位达人知道的话请告知。
解释::(1)A在UI线程上执行完后,开始BeginInvoke,BeginInvoke是异步
(2)InvokeMethod方法,即代码段C不会执行,而是立即在UI线程上执行代码段B。
(3)代码段B执行完后(就是说butBeginInvoke_Click方法执行完后),InvokeMethod方法,即代码段C才在UI线程上继续执行。

由此,我们知道:
Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行的。也就是说如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死。

那么,这个异步到底是什么意思呢?

异步是指相对于调用BeginInvoke的线程异步,而不是相对于UI线程异步,你在UI线程上调用BeginInvoke ,当然不行了。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。
BeginInvoke的原理是将调用的方法Marshal成消息,然后调用Win32 API中的RegisterWindowMessage()向UI窗口发送消息。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。

(二)我们用Thread来调用BeginInvoke和Invoke
我们开一个线程,让线程执行一些耗费时间的操作,然后再用Control.Invoke和Control.BeginInvoke到用户UI线程,执行界面更新。

代码(三) Thread调用Control的Invoke

private Thread invokeThread;
private delegate void invokeDelegate();
private void StartMethod(){
//C代码段......
Control.Invoke(new invokeDelegate(invokeMethod));
//D代码段......
}
private void invokeMethod(){
//E代码段
}
private void butInvoke_Click(object sender, EventArgs e) {
//A代码段.......
invokeThread = new Thread(new ThreadStart(StartMethod));
invokeThread.Start();
//B代码段......
}

你觉得代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A------>(Start一开始B和StartMethod的C就同时执行)---->(C执行完了,不管B有没有执行完,invokeThread把消息封送(invoke)给UI线程,然后自己等待)---->UI线程处理完butInvoke_Click消息后,处理invokeThread封送过来的消息,执行invokeMethod方法,即代码段E,处理往后UI线程切换到invokeThread线程。
这个Control.Invoke是相对于invokeThread线程同步的,阻止了其运行。

解释:
1。UI执行A
2。UI开线程InvokeThread,B和C同时执行,B执行在线程UI上,C执行在线程invokeThread上。
3。invokeThread封送消息给UI,然后自己等待,UI处理完消息后,处理invokeThread封送的消息,即代码段E
4。UI执行完E后,转到线程invokeThread上,invokeThread线程执行代码段D

代码(四) Thread调用Control的BeginInvoke


private Thread beginInvokeThread;
private delegate void beginInvokeDelegate();
private void StartMethod(){
//C代码段......
Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod));
//D代码段......
}
private void beginInvokeMethod(){
//E代码段
}
private void butBeginInvoke_Click(object sender, EventArgs e) {
//A代码段.......
beginInvokeThread = new Thread(new ThreadStart(StartMethod));
beginInvokeThread .Start();
//B代码段......
}
你觉得代码的执行顺序是什么呢?记好Control的Invoke和BeginInvoke都执行在主线程即UI线程上
A在UI线程上执行----->beginInvokeThread线程开始执行,UI继续执行代码段B,并发地invokeThread执行代码段C-------------->不管UI有没有执行完代码段B,这时beginInvokeThread线程把消息封送给UI,单自己并不等待,继续向下执行-------->UI处理完butBeginInvoke_Click

消息后,处理beginInvokeThread线程封送过来的消息。


解释:
1。UI执行A
2。UI开线程beginInvokeThread,B和C同时执行,B执行在线程UI上,C执行在线程beginInvokeThread上。
3。beginInvokeThread封送消息给UI,然后自己继续执行代码D,UI处理完消息后,处理invokeThread封送的消息,即代码段E
有点疑问:如果UI先执行完毕,是不是有可能过了段时间beginInvokeThread才把消息封送给UI,然后UI才继续执行封送的消息E。如图浅绿的部分。


Control的BeginInvoke是相对于调用它的线程,即beginInvokeThread相对是异步的。
因此,我们可以想到。如果要异步取耗费长时间的数据,比如从数据库中读大量数据,我们应该这么做。
(1)如果你想阻止调用线程,那么调用代码(三),代码段D删掉,C改为耗费长时间的操作,因为这个操作是在另外一个线程中做的。代码段E改为更新界面的方法。
(2)如果你不想阻止调用线程,那么调用代码(四),代码段D删掉,C改为耗费长时间的操作,因为这个操作是在另外一个线程中做的。代码段E改为更新界面的方法。

原文地址:http://www.cnblogs.com/whssunboy/archive/2007/06/07/775319.html

0
0
0
(请您对文章做出评价)
posted @ 2009-07-09 09:43 VelvetMark 阅读(132) 评论(1) 编辑 收藏 网摘

<!-- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"> <rdf:Description rdf:about="http://www.cnblogs.com/yuxuanji/archive/2009/07/09/1519605.html" dc:identifier="http://www.cnblogs.com/yuxuanji/archive/2009/07/09/1519605.html" dc:title="" trackback:ping="http://www.cnblogs.com/yuxuanji/services/trackbacks/1519605.aspx" /> </rdf:RDF> --><!--end: topics 文章、评论容器-->

<!--done-->
1580108
#1楼[楼主]2009-07-09 12:06 | VelvetMark
做了一个有趣的实验
using System;

using System.Windows.Forms;

namespace Test.Control.BeginInvoke
{
public partial class Form1 : Form
{
private string result = "";
public Form1()
{
InitializeComponent();

}
int i = 0;

private delegate void InvokeDelegate();

private void Message1()
{
MessageBox.Show("Message 1");
}

private void Message2()
{
MessageBox.Show("Message 2");
}

private void button1_Click(object sender, EventArgs e)
{
TestMessageBoxShow1();

}
private void button2_Click(object sender, EventArgs e)
{
TestMessageBoxShow2();
}
private void TestMessageBoxShow1()
{
// Code Section A


// Code Section C
this.BeginInvoke(new InvokeDelegate(Message1));

// Code Sectino B
this.Message2();
}

private void TestMessageBoxShow2()
{
// Code Section C
this.BeginInvoke(new InvokeDelegate(String_Message1));

// Code Sectino B


this.String_Message2();

MessageBox.Show(string.Format("i={0},result={1}",i,result));
MessageBox.Show(string.Format("i={0},result={1}", i, result));

}

private void String_Message1()
{
//MessageBox.Show(result);
result += "Message1";
++i;

}
private void String_Message2()
{
result += "Message2";
++i;
}


}
}

当点击button1,先显示Message1,再显示Message2
当点击button2,先显示i=1,result=Message2.紧接着显示i=2,result=Message2Message1
TestMessageBoxShow2中两次调用同样方法,但显示结果不同,
这能否说明BeginInvoke中假如某异步操作结果能先返回到主线程,则优先处理这个异步操作.比如String_Message1方法中如果去掉MessageBox.Show(result)的注释,会显示Message2.而UI线程中第一次调用MessageBox.Show(...)只是呼唤异步调用中的操作结果result,所以一次只显示i=1,result=Message2,主线程再次调用MessageBox.Show(...)则把异步操作的结果全部显示出来显示i=2,result=Message2Message1。听说Control.BeginInvoke并未开辟新线程,那这是否是异步调用的奥妙呢
分享到:
评论

相关推荐

    C#窗体中Invoke和BeginInvoke方法详解

    Invoke and BeginInvoke `Invoke`和`BeginInvoke`方法同样可用于线程间通信,特别是在.NET环境中。它们通过委托来传递要执行的操作,确保所有GUI更新都在正确的线程中进行。这两种方法的使用取决于具体场景的需求...

    Invoke-and-BeginInvoke.rar_The Difference_invoke

    在Windows Forms或WPF等UI环境中,由于UI元素(如控件)只能在其创建的线程(即UI线程)中进行修改,因此,当需要在后台线程更新UI时,`Invoke`和`BeginInvoke`就显得尤为重要。这两个方法都是`Control`类的成员,...

    Network programming.NET with C# and VB.NET 2004.rar

    这本书详细介绍了如何利用这两种语言来开发网络应用,涵盖了API调用、DLLImport特性以及Invoke方法等多个关键知识点。 首先,API(应用程序接口)是操作系统提供给程序员调用的一系列函数和方法,让开发者能够实现...

    VB.NET中实现文本的复制粘贴(调用剪贴板)

    可以使用Control.Invoke或BeginInvoke方法确保操作在正确的线程上执行。 5. 其他数据格式: 除了文本外,剪贴板还可以处理其他类型的数据,如图像、文件列表等。只需将DataFormats更改为相应的格式即可。例如,使用...

    C#多线程不阻塞[参照].pdf

    // modify the code to wait 30 seconds and report the available threads // in the thread pool ``` 在这篇文章中,我们讨论了异步方法调用、委托、线程池和异步编程的优点。我们也探讨了同步方法调用和异步方法...

    A tutorial on connections points and Aynchronous calls(66KB)

    例如,`BeginInvoke`和`EndInvoke`方法在.NET Framework中用于控制台和Windows Forms应用程序的异步方法调用。而在COM编程中,`IMethodCallMessage`和`IMessageSink`接口可用于实现异步调用的基础设施。 现在,让...

    进度栏,线程,Windows窗体和您

    它可能会涵盖以下内容:创建新的线程,启动和停止线程,使用ThreadSafe的方式来更新UI(如通过Invoke或BeginInvoke方法),以及如何在后台线程中计算进度并同步到进度条控件。 "ProgressBarExample.zip"和...

    Synchronous And Asynchronous Event Handling For Controls Created At Runtime

    3. **UI更新**: 在异步操作完成后更新UI时,确保在正确的上下文中执行,例如使用`Control.Invoke`或`Control.BeginInvoke`。 4. **资源管理**: 确保异步操作完成后释放任何分配的资源。 5. **性能监控**: 定期检查并...

    c#各种拖拽实例.zip

    这可能涉及到使用`Control.Invoke`或`Control.BeginInvoke`来确保在正确的线程上更新UI。 9. **安全性与权限**: - 某些情况下,拖放操作可能受到安全策略的限制,尤其是在网络应用或沙箱环境中。开发者需要了解...

    AsyncWorker-一个Typesafe BackgroundWorker(以及有关线程的一般知识)

    这可以通过使用控件的`Invoke`或`BeginInvoke`方法来实现。 5. **错误处理**:AsyncWorker也支持异常处理。如果后台任务抛出异常,`RunWorkerCompleted`事件的`Error`属性将包含该异常,这样开发者可以在UI线程中...

    从SQL加载多行并为每行创建标签

    在处理每一行数据时,确保更新UI是线程安全的,可能需要在UI线程中执行,可以使用`Control.Invoke`或`Control.BeginInvoke`方法。 5. **性能优化**:如果数据量大,一次性加载所有数据可能会导致内存压力。可以考虑...

    多线程共享功能和子

    可以使用`Control.Invoke`或`Control.BeginInvoke`方法在UI线程上执行操作。这些方法会确保在正确的上下文中执行代码。 4. **线程同步** 当你需要控制线程执行顺序或者避免资源冲突时,可以使用`Thread.Join`等待...

Global site tag (gtag.js) - Google Analytics