0

0

invoke和begininvoke 区别——c#

蓮花仙者

蓮花仙者

发布时间:2025-07-21 08:12:17

|

565人浏览过

|

来源于php中文网

原创

本文转自:https://cloud.tencent.com/developer/article/1759131

https://www.cnblogs.com/worldreason/archive/2008/06/09/1216127.html

invoke和begininvoke 区别

一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解。

首先说下,invoke和begininvoke的使用有两种情况:

control中的invoke、begininvoke。delegrate中的invoke、begininvoke。

这两种情况是不同的,我们这里要讲的是第1种。下面我们在来说下.NET中对invoke和begininvoke的官方定义。

control.invoke(参数delegate)方法:在拥有此控件的基础窗口句柄的线程上执行指定的委托。

control.begininvoke(参数delegate)方法:在创建控件的基础句柄所在线程上异步执行指定委托。

根据这两个概念我们大致理解invoke表是同步、begininvoke表示异步。

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

如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。

我们来做一个测试。

invoke 例子:

代码语言:javascript代码运行次数:0运行复制
private void button1_Click(object sender, EventArgs e){            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA");            invokeThread = new Thread(new ThreadStart(StartMethod));            invokeThread.Start();            string a = string.Empty;            for (int i = 0; i < 3; i++)      //调整循环次数,看的会更清楚            {                Thread.Sleep(1000);                   a = a + "B";            }            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a);} private void StartMethod(){            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC");            button1.Invoke(new invokeDelegate(invokeMethod));              MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD");} private void invokeMethod(){            //Thread.Sleep(3000);            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEE");} 

结论:我们运行后,看下程序的运行顺序,1AAA->3CCC和1BBB->1EEE ->3DDD 。

解释:主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过invoke来将invokemethod方法提交给主线程,然后子线 程等待主线程执行,直到主线程将invokemethod方法执行完成(期间必须等待主线程的任务执行完成,才会去执行invoke提交的任务),最后执 行子线程3DDD。

begininvoke 例子:

代码语言:javascript代码运行次数:0运行复制
private void button1_Click(object sender, EventArgs e){            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"AAA");            invokeThread = new Thread(new ThreadStart(StartMethod));            invokeThread.Start();            string a = string.Empty;            for (int i = 0; i < 3; i++)      //调整循环次数,看的会更清楚            {                Thread.Sleep(1000);                   a = a + "B";            }            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+a);} private void StartMethod(){            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"CCC");            button1.BeginInvoke(new invokeDelegate(invokeMethod));              MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString()+"DDD");} private void beginInvokeMethod()        {            //Thread.Sleep(3000);            MessageBox.Show(Thread.CurrentThread.GetHashCode().ToString() + "EEEEEEEEEEEE");        }

结论: 我们运行后看看执行的结果:1AAA->1BBB和3CCC->1EEE和3DDD。

解释: 主线程运行1AAA,然后1BBB和子线程3CCC同时执行,然后通过begininvoke来将invokemethod方法提交给主线程,然后主线程执行1EEE(主线程自己的任务执行完成), 同时子线程继续执行3DDD。

通过这个两段代码的测试比较,我们会发现其实invoke和begininvoke所提交的委托方法都是在主线程中执行的,其实根据我invoke 和begininvoke的定义我们要在子线程中来看这个问题,在invoke例子中我们会发现invoke所提交的委托方法执行完成后,才能继续执行 DDD;在begininvoke例子中我们会发现begininvoke所提交的委托方法后,子线程讲继续执行DDD,不需要等待委托方法的完成。 那么现在我们在回想下invoke(同步)和begininvoke(异步)的概念,其实它们所说的意思是相对于子线程而言的,其实对于控件的调用总是由 主线程来执行的。我们很多人搞不清这个同步和异步,主要还是因为我们把参照物选错了。其实有时候光看概念是很容易理解错误的。

解决从不是创建控件的线程访问它

在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。

正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。

而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。

举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox..

using System.Threading;

//启动一个线程

Thread thread=new Thread(new ThreadStart(DoWork));

thread.Start();

//线程方法

private void DoWork()

{

this.TextBox1.Text="我是一个文本框";

}

如果你像上面操作,在VS2005或2008里是会有异常的...

正确的做法是用Invoke\BeginInvoke

using System.Threading;

namespace test

{

public partial class Form1 : Form

{

public delegate void MyInvoke(string str1,string str2);

public Form1()

{

InitializeComponent();

}

企业黄页-大众投资指南整站 asp.net 2.0
企业黄页-大众投资指南整站 asp.net 2.0

大众投资指南是基于Asp.Net(2.0)+C#+Access(sql2000)的企业黄页类程序,是基于web2.0 模式的网站。 贴吧和黄页都有采集功能 主程序包括分类信息和商家黄页两大模块。分类信息支持二级分类,商家黄页支持二级地区分类及二级行业分类。程序采用了伪静态(url重写)技术,可选生成纯静态首页。 一、分类信息仿百度贴吧编写,可以分别对游客及会员设置不同的审核条件。会员发布信息

下载

public void DoWork()

{

MyInvoke mi = new MyInvoke(UpdateForm);

this.BeginInvoke(mi, new Object[] {"我是文本框","haha"});

}

public void UpdateForm(string param1,string parm2)

{

this.textBox1.Text = param1+parm2;

}

private void button1_Click(object sender, EventArgs e)

{

Thread thread = new Thread(new ThreadStart(DoWork));

thread.Start();

}

}

}

注意代理的使用!

后面再次补充

在 WinForm开发过程中经常会用到线程,有时候还往往需要在线程中访问线程外的控件,比如:设置textbox的Text属性等等。如果直接设置程序必 定会报出:从不是创建控件的线程访问它,这个异常。通常我们可以采用两种方法来解决。一是通过设置control的属性。二是通过delegate,而通 过delegate也有两种方式,一种是常用的方式,另一种就是匿名方式。下面分别加以说明.

首先,通过设置control的一个属性值为false.我们可以在Form_Load方法中添加:Control.CheckForIllegalCrossThreadCalls=false;来解决。设置为false表示不对错误线程的调用进行捕获。这样在线程中对textbox的Text属性进行设置时就不会再报错了。

其次,通过delegate的方法来解决。

普通的委托方法例如:

代码语言:javascript代码运行次数:0运行复制
delegate void SafeSetText(string strMsg);private void SetText(string strMsg){ if(textbox1.InvokeRequired) {            SafeSetText objSet=new SafeSetText(SetText);            textbox1.Invoke(objSet,new object[]{strMsg}); } else {   textbox1.Text=strMsg; }}

在线程内需要设置textbox的值时调用SetText方法既可。我们还可以采用另一种委托的方式来实现,那就是匿名代理,例如:

代码语言:javascript代码运行次数:0运行复制
delegate void SafeSetText(string strMsg);private void SetText2(string strMsg){SafeSetText objSet = delegate(string str)   {       textBox1.Text = str;   }   textBox1.Invoke(objSet,new object[]{strMsg});}

这样同样可以实现。

个人觉得还是采用代理好些。

在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及以后版本更能用Action封装方法。例如以下写法可以看上去非常简洁:

void ButtonOnClick(object sender,EventArgs e)

{

代码语言:txt复制
this.Invoke(new Action(()=>
代码语言:txt复制
{
代码语言:txt复制
    button.Text="关闭";
代码语言:txt复制
}));

}

最新:

Invoke(() =>

{

button.Text="关闭";

});

一、为什么Control类提供了Invoke和BeginInvoke机制?

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

1、windows程序消息机制

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

invoke和begininvoke 区别——c#
代码语言:txt复制
                                              Windows GUI程序的消息循环

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

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

2、dotnet里面的消息循环

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接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新GUI界面控件的机制。

public interface ISynchronizeInvoke

{

代码语言:txt复制
    [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
代码语言:txt复制
    IAsyncResult BeginInvoke(Delegate method, object[] args);
代码语言:txt复制
    object EndInvoke(IAsyncResult result);
代码语言:txt复制
    object Invoke(Delegate method, object[] args);
代码语言:txt复制
    bool InvokeRequired { get; }

}

}

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

二、消息机制---线程间和进程间通信机制1、window消息发送

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

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

invoke和begininvoke 区别——c#

用消息机制通信

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

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

2、Invoke and BeginInvoke
invoke和begininvoke 区别——c#
代码语言:txt复制
                                                    Invoke or BeginInvoke

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类对这一属性的实现就是在判断调用者和控件是否属于同一个线程的。

三、Delegate.BeginInvoke

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

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

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

四、用Reflector察看一些相关代码1、Control.BeginInvoke and Control.Invoke代码语言:javascript代码运行次数:0运行复制
public IAsyncResult BeginInvoke(Delegate method, params object[] args)
代码语言:javascript代码运行次数:0运行复制
{
代码语言:javascript代码运行次数:0运行复制
    using (new MultithreadSafeCallScope())
代码语言:javascript代码运行次数:0运行复制
    {
代码语言:javascript代码运行次数:0运行复制
        return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);
代码语言:javascript代码运行次数:0运行复制
    }
代码语言:javascript代码运行次数:0运行复制
}
代码语言:javascript代码运行次数:0运行复制
public object Invoke(Delegate method, params object[] args)
代码语言:javascript代码运行次数:0运行复制
{
代码语言:javascript代码运行次数:0运行复制
    using (new MultithreadSafeCallScope())
代码语言:javascript代码运行次数:0运行复制
    {
代码语言:javascript代码运行次数:0运行复制
        return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);
代码语言:javascript代码运行次数:0运行复制
    }
代码语言:javascript代码运行次数:0运行复制
}

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

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

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

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

3、InvokeRequired代码语言:javascript代码运行次数:0运行复制
public bool InvokeRequired
代码语言:javascript代码运行次数:0运行复制
{
代码语言:javascript代码运行次数:0运行复制
    get
代码语言:javascript代码运行次数:0运行复制
    {
代码语言:javascript代码运行次数:0运行复制
        using (new MultithreadSafeCallScope())
代码语言:javascript代码运行次数:0运行复制
        {
代码语言:javascript代码运行次数:0运行复制
            HandleRef ref2;
代码语言:javascript代码运行次数:0运行复制
            int num;
代码语言:javascript代码运行次数:0运行复制
            if (this.IsHandleCreated)
代码语言:javascript代码运行次数:0运行复制
            {
代码语言:javascript代码运行次数:0运行复制
                ref2 = new HandleRef(this, this.Handle);
代码语言:javascript代码运行次数:0运行复制
            }
代码语言:javascript代码运行次数:0运行复制
            else
代码语言:javascript代码运行次数:0运行复制
            {
代码语言:javascript代码运行次数:0运行复制
                Control wrapper = this.FindMarshalingControl();
代码语言:javascript代码运行次数:0运行复制
                if (!wrapper.IsHandleCreated)
代码语言:javascript代码运行次数:0运行复制
                {
代码语言:javascript代码运行次数:0运行复制
                    return false;
代码语言:javascript代码运行次数:0运行复制
                }
代码语言:javascript代码运行次数:0运行复制
                ref2 = new HandleRef(wrapper, wrapper.Handle);
代码语言:javascript代码运行次数:0运行复制
            }
代码语言:javascript代码运行次数:0运行复制
            int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
代码语言:javascript代码运行次数:0运行复制
            int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
代码语言:javascript代码运行次数:0运行复制
            return (windowThreadProcessId != currentThreadId);
代码语言:javascript代码运行次数:0运行复制
        }
代码语言:javascript代码运行次数:0运行复制
    }

}

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

483

2023.08.02

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

97

2023.09.25

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

177

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

99

2025.11.27

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

28

2026.01.06

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1155

2023.10.19

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Rust 教程
Rust 教程

共28课时 | 5.1万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号