C#中 Invoke和BeginInvoke的区别详解

这篇具有很好参考价值的文章主要介绍了C#中 Invoke和BeginInvoke的区别详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Invoke和BeginInvoke方法,主要用于子线程操作主线程UI的控件,修改控件属性等。

(1)Invoke同步运行,会等待代码运行完之后返回,线程会阻塞。

(2)BeginInvoke异步运行,调用之后立即返回,不会等待修改控件代码执行完。不会阻塞。

故由上述可知,Invoke和BeginInvoke方法由ISynchronizeInvoke接口提供,且都需要一个委托对象作为参数。委托类似于回调函数的地址,调用者可通过这两个方法将需要调用的函数地址封送到界面线程。如果这些封送给界面线程的方法包含了更改界面控件的代码,由于最终执行这个方法是在界面控件线程中,从而避免了线程之间的竞争。

(3)使用Invoke方法完成委托的封送,类似于使用SendMessage方法给界面线程发送消息,为同步方法。在Invoke封装的方法未执行完毕之前,Invoke方法是不会返回,使调用者线程被阻塞。

(4)使用BeginInvoke方法完成委托的封送,类似于使用PostMessage方法给界面线程发送消息,为异步方法。在完成委托的封送后,BeginInvoke方法立即返回,不会等待委托的方法执行完毕,调用者线程将不会阻塞。但是调用者可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。

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

二、使用场合

若你的后台线程在更新UI界面且不需要等待,则应该使用BeginInvoke方法进行异步操作。

若你的后台线程在更新UI界面并且需要等待,则应该使用Invoke方法进行同步操作。

在ISynchronizeInvoke接口中,可以看到还有一个InvokeRequired。这个属性的作用是:在编程时确定一个对象在访问UI控件时是否需要使用Invoke和BeginInvoke方法进行封送,如果不需要则可以直接进行更新。在调用者对象和UI对象属于同一个线程时,该属性返回false。Control类对这一属性的实现是在判断调用者和控件是否同属于一个线程。

Delegate.BeginInvoke

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

在Control类中的BeginInvoke方法没有开辟新的线程完成委托任务,而是让界面所属的控件线程完成委托任务(异步操作就是开辟新的线程的说法也不一定准确)。

Control.BeginInvoke和Control.Invoke

public IAsyncResult BeginInvoke(Delegate method, params object[] args)

{

using (new MultithreadSafeCallScope())

{

return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);

}文章来源地址https://www.toymoban.com/news/detail-803683.html

}

public object Invoke(Delegate method, params object[] args)

{

using (new MultithreadSafeCallScope())

{

return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);

}

}

从上述代码可以看到Invoke和BeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样,一个为true,另一个为false。

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

三、下面是详细解释

在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate。

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

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

1.1 Windows程序消息机制

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

Windows GUI程序的消息循环

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

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

1.2 Dotnet 消息循环

.

public static void Main(string[] args)

{

Form f = new Form();

Application.Run(f);

}

.

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

1.3 线程外操作GUI控件的问题

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

因此,Dotnet为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新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方法,通过一个委托把调用封送到控件所属的线程上执行。

2. 消息机制:线程间和进程间通信机制

2.1 Window消息发送

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

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

用消息机制通信

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

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

2.2 Invoke and BeginInvoke

Invoke or BeginInvoke

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

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

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

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

2.3 使用场合问题

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

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

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

3. Delegate.BeginInvoke

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

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

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

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

4. C#多线程 Invoke方法的使用

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

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

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

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

.

using System.Threading;

public delegate void MyInvoke(string str);

private void btnStartThread_Click(object sender, EventArgs e)

{

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

thread.Start();

}

public void DoWord()

{

MyInvoke mi = new MyInvoke(SetTxt);

BeginInvoke(mi, new object[] { “abc” });

}

public void SetTxt(string str)

{

txtReceive.Text += str + System.Environment.NewLine;

}

到了这里,关于C#中 Invoke和BeginInvoke的区别详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# BeginInvoke 加 EndInvoke实现异步操作

    1、 定义一个委托 2、 需异步操作的函数 3、 回调方法,在异步方法执行完后才执行 ,接收返回结果 4、 异步调用:

    2024年02月13日
    浏览(26)
  • C# 中的Invoke 方法

    问题的引发: ?.Invoke()是什么意思。 答案如下: 同时又想到UI线程那块,也有invoke,区别是什么?故此总结如下: 在 C# 中,Invoke 方法可用于委托调用、控制 UI 线程操作和反射调用等多种情况。具体使用方法取决于上下文和所涉及的类型。 1、委托的 Invoke 方法:委托类型具有

    2024年02月13日
    浏览(27)
  • C# 中的Invoke方法

    Invoke 是委托类型的实例方法,用于调用委托所引用的方法。委托是一种类型,它允许我们将方法作为参数传递并存储在字段或属性中。当委托实例被调用时,它会调用与之关联的方法。可以使用 += 运算符将一个方法添加到委托中,使用 -= 运算符将其从委托中删除。 在具体使

    2024年02月13日
    浏览(27)
  • C#中的Invoke

    在 C# 中, Invoke() 是一个用于调用方法的方法,它能够在运行时动态地调用一个方法。 Invoke() 方法的使用方式有两种: 通过 MethodInfo 对象调用: 在上面的示例中,我们首先通过 typeof(MyStore).GetMethod(\\\"MyMethod\\\") 获取了 Open 方法的 MethodInfo 对象,然后使用 Invoke() 方法调用该方法。

    2024年02月14日
    浏览(36)
  • 【Unity入门】19.定时调用Invoke

        大家好,我是Lampard~~     欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢  (1)Invoke 单词调用     计时器我们并不陌生,在cocos上有着schedule类是相似的。在Unity中给我们提供了Invoke相关的类来实现计时器功能     首先我们熟悉的是 单次延时调用方法Inv

    2024年02月03日
    浏览(29)
  • unity 等待事件之协程和Invoke

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:这里可以添加本文要记录的大概内容: 协程的用法 和 Invoke 的等待事件使用 提示:以下是本篇文章正文内容,下面案例可供参考 代码如下(示例): 好记性不如烂笔头!

    2024年04月13日
    浏览(29)
  • PowerShell Invoke-WebRequest简单使用

    该例子模拟请求接口,并对接收的数据进行json格式化,并取出其中的属性值 注意: Invoke-WebRequest 还有很多参数,比如可以设置请求头 -Headers 、设置请求体 -Body 等等 更多细节请参考Invoke-WebRequest

    2024年02月11日
    浏览(65)
  • 【Power Shell】Invoke-Expression ,Invoke-Expression -Command $activateCommand;错误或power shell激活虚拟环境报错失败

    power Shell 一打开就有红字错误: 这边我使用了很多 无用 的办法: ①conda init ②conda install -n root -c pscondaenvs pscondaenvs ③把 Invoke-Expression -Command $activateCommand; 改成 后来我在查看的时候发现他们很多都有一种思路,那就是系统环境变量出了问题; 所以我顺着这个思路进行了修改

    2024年01月16日
    浏览(30)
  • Unity 之 Invoke 与InvokeRepeting 函数控制定时调用

    在Unity游戏开发中, Invoke 是一种用于延迟调用方法的方法。它允许你在一定的时间之后执行特定的函数或方法,通常用于执行定时任务,例如在一段时间后触发一个事件或在一定间隔内重复执行某个方法。 Invoke 方法的详细介绍如下: 函数签名: methodName :要调用的方法的名

    2024年02月19日
    浏览(28)
  • Unity中如何激发(invoke)一个Button的OnClick事件

    点了【按钮2】后,会激发(invoke)【按钮1】的OnClick事件 1、用Invoke()激发 2、用ExecuteEvents.Execute()分发

    2024年02月12日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包