Control.Invoke和Control.BeginInvoke

问题的引入

下面有个简单的demo,大家一看代码就知道效果如何示例。我新建一个winform的程序,然后写入了如下代码:
using System;
using System.Windows.Forms;

namespace MyExampleList
{
    public partial class ControlInvokeExample : Form
    {
        public ControlInvokeExample()
        {
            InitializeComponent();
        }

        private void btnSayHello_Click(object sender, EventArgs e)
        {
            txtHello.Text = "祝你学习C#愉快";
        }
    }
}
运行效果如下: [caption id="attachment_1150" align="alignnone" width="364"]显示文本 显示文本[/caption] 下面我来改造一下这个简单的demo。我们在编程的时候使用多线程是非常的常见,我们在点击按钮的时候启用另一个线程,然后来操作textbox的值,让它进行变化,代码如下:
using System;
using System.Threading;
using System.Windows.Forms;

namespace MyExampleList
{
    public partial class ControlInvokeExample : Form
    {
        public ControlInvokeExample()
        {
            InitializeComponent();
        }

        private void btnSayHello_Click(object sender, EventArgs e)
        {
            Thread th=new Thread(new ThreadStart(StartMethord));
            th.Start();

        }

        private void StartMethord()
        {
            txtHello.Text = "祝你学习C#愉快";
        }
    }
}
理论上就应该是上面的代码就可以执行的,但是实际上会报错,具体错误如下图所示:   [caption id="attachment_1152" align="alignnone" width="860"]线程交互错误 线程交互错误[/caption] 错误解释:上面的错误意思就是txtHello这个控件不是th创建的所以th线程不能直接访问它,因此报错了。 那我们如何让th线程也能改变UI中的txtHello这个textbox的值呢? 我们通过Control.Invoke或者Control.BeginInvoke来实现。

Control.Invoke和Control.BeginInvoke

Control.Invoke:在拥有此控件的基础窗口句柄的线程上执行指定的委托。 Control.BeginInvoke:在创建控件的基础句柄所在线程上异步执行指定委托。 根据上面的解释我们来修改一下上面出错的代码,用Invoke的方式来实现th线程更改txtHello的内容,具体代码如下(我称下面的这种实现方式为:A):
using System;
using System.Threading;
using System.Windows.Forms;

namespace MyExampleList
{
    public partial class ControlInvokeExample : Form
    {
        public ControlInvokeExample()
        {
            InitializeComponent();
        }
        delegate void InvokeDelegate();
        private void btnSayHello_Click(object sender, EventArgs e)
        {
            Thread th=new Thread(new ThreadStart(StartMethord));
            th.Start();
        }
        private void StartMethord()
        {
            txtHello.Invoke(new InvokeDelegate(ChangeTextBox));
        }
        private void ChangeTextBox()
        {
            txtHello.Text = "祝你学习C#愉快";
        }
    }
}
用BeginInvoke的方式来实现th线程更改txtHello的内容,具体代码如下(我称下面的这种实现方式为:B):
using System;
using System.Threading;
using System.Windows.Forms;

namespace MyExampleList
{
    public partial class ControlInvokeExample : Form
    {
        public ControlInvokeExample()
        {
            InitializeComponent();
        }
        delegate void InvokeDelegate();
        private void btnSayHello_Click(object sender, EventArgs e)
        {
            Thread th=new Thread(new ThreadStart(StartMethord));
            th.Start();
        }
        private void StartMethord()
        {
            txtHello.BeginInvoke(new InvokeDelegate(ChangeTextBox));
        }
        private void ChangeTextBox()
        {
            txtHello.Text = "祝你学习C#愉快";
        }
    }
}
我们对比一下A和B两种实现方式,在代码上的区别,唯一的区别就是A使用了txtHello.Invoke和B方式用了txtHello.BeginInvoke,也就是他们的区别又归到了Invoke和BeginInvoke上,一个是同步,一个是异步,也就是A方式的时候是等待Invoke的执行结果,而B方式的时候是直接执行下去不等待BeginInvoke的执行结果。在本例的demo中方式A和B实现的效果相同,但是原理不同,仅此而已。下面我来重新写一个新的demo,让大家见识一下这两个方法的区别。 修改后的实例代码如下:
using System;
using System.Threading;
using System.Windows.Forms;

namespace MyExampleList
{
    public partial class ControlInvokeExample : Form
    {
        public ControlInvokeExample()
        {
            InitializeComponent();
        }
        delegate void InvokeDelegate();
        private void btnSayHello_Click(object sender, EventArgs e)
        {
            Thread th=new Thread(new ThreadStart(StartMethord));
            th.Start();

        }
        private void StartMethord()
        {
            DateTime dt1 = DateTime.Now;
            txtHello.Invoke(new InvokeDelegate(ChangeTextBox));//等待ChangeTextBox方法执行完毕,所以str的值比较大
            // txtHello.BeginInvoke(new InvokeDelegate(ChangeTextBox));//不等待ChangeTextBox方法执行完毕,所以str的值会很小
            string str=  (DateTime.Now - dt1).TotalMilliseconds.ToString();

        }
        private void ChangeTextBox()
        {
            Thread.Sleep(3000);
            txtHello.Text = "祝你学习C#愉快"+DateTime.Now.ToString();
        }
    }
}
运行后的结果如下两图: [caption id="attachment_1154" align="alignnone" width="630"]invoke结果 invoke结果[/caption] [caption id="attachment_1155" align="alignnone" width="584"]BeginInvoke结果    BeginInvoke结果[/caption] 差距很明显了吧。

那么什么时候我们必须使用Invoke和BeginInvoke呢?

InvokeRequired:获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程(本例中的UI线程)以外的线程(自定义线程th)中。 也就是当InvokeRequired为true时我们必须用Invoke或者BeginInvoke方法来更新界面。  

特别注意

Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行的。也就是说如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死。 那么,这个异步到底是什么意思呢?(具体的解释可以看上面的A和B方式,指的是th线程的异步) 异步是指相对于调用BeginInvoke的线程异步,而不是相对于UI线程异步,你在UI线程上调用BeginInvoke ,当然不行了。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。 BeginInvoke的原理是将调用的方法Marshal成消息,然后调用Win32 API中的RegisterWindowMessage()向UI窗口发送消息。----摘自"Invoke和BeginInvoke的真正涵义"一文中的评论。
原文地址:https://www.cnblogs.com/vsdot/p/3263430.html