C#Delegate.Invoke、Delegate.BeginInvoke And Control.Invoke、Control.BeginInvoke

一、Delegate的Invoke、BeginInvoke

1、Delegate.Invoke (委托同步调用)

  a、委托的Invoke方法,在当前线程中执行委托。

  b、委托执行时阻塞当前线程,知道委托执行完毕,当前线程才继续向下执行。

  c、委托的Invoke方法,类似方法的常规调用。

2、Delegate.BeginInvoke (委托异步调用)

  a、委托的BeginInvoke方法,在线程池分配的子线程中执行委托

  b、委托执行时不会阻塞主线程(调用委托的BeginInvoke线程),主线程继续向下执行。

  c、委托执行时会阻塞子线程。

  d、委托结束时,如果有返回值,子线程讲返回值传递给主线程;如果有回调函数,子线程将继续执行回调函数。

3、Demo

  a、Delegate

复制代码

 1         private void btn_General_Click(object sender, EventArgs e)
 2         {
 3             txt_Message.Text = "";
 4             txt_Message.Text += "主线程:"+ Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 5             //委托方法,在调用委托的线程中执行,本例中就是主线程(UI线程)。
 6             //执行一些耗时的操作,就会阻塞主线程(UI线程)
 7             //委托的普通调用就等于方法的直接调用,del();等价于SomeWork();
 8             del();
 9             //SomeWork();
10             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
11         }

复制代码

   b、Delegate.Invoke

复制代码

1         private void btn_Main_Invoke_Click(object sender, EventArgs e)
2         {
3             txt_Message.Text = "";
4             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
5             //委托的同步调用,其实就是等价于委托的普通调用。
6             del.Invoke();
7             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
8         }

复制代码

复制代码

 1         private void btn_Sub_Invoke_Click(object sender, EventArgs e)
 2         {
 3             txt_Message.Text = "";
 4             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 5 
 6             //开启新的线程执行委托,主线程(UI线程)继续向下执行
 7             new Thread(() => {
 8                 txt_Message.Text += "\r\n----子线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 9                 //委托在调用线程中执行,并阻塞调用线程,知道委托方法执行结束。
10                 del.Invoke();
11                 txt_Message.Text += "\r\n----子线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
12             }).Start();
13 
14             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
15         }

复制代码

  c、Delegate.BeginInvoke

复制代码

 1         private void btn_Main_BeginInvoke_Click(object sender, EventArgs e)
 2         {
 3             txt_Message.Text = "";
 4             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 5             //委托异步调用
 6             //1、委托方法,在线程池中分配的子线程中执行。
 7             //2、主线程和子线程同时执行。
 8             //3、子线程结束之后,如果有返回值得话,将返回值传递给主线程。如果有回调函数的话,继续在子线程中执行回调函数。
 9 
10             //有异常,控件不能在子线程中访问修改。
11             //避免这类异常有两种方法   
12             //1、手动关闭控件的跨线程安全检查Control.CheckForIllegalCrossThreadCalls = false;(不建议使用)
13             //2、使用控件的Invoke方法。(推荐使用)
14             del.BeginInvoke(null,null);
15             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
16         }

复制代码

复制代码

 1         private void btn_Sub_BeginInvoke_Click(object sender, EventArgs e)
 2         {
 3             txt_Message.Text = "";
 4             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 5 
 6             //开启新的线程执行委托,主线程(UI线程)继续向下执行
 7             new Thread(() =>
 8             {
 9                 txt_Message.Text += "\r\n----子线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
10                 //在线程池中分配的子线程中执行委托方法,调用委托的线程继续向下执行。
11                 del.BeginInvoke(null, null); 
12                 txt_Message.Text += "\r\n----子线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
13             }).Start();
14 
15             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
16         }

复制代码

 




 

二、Control的Invoke、BeginInvoke

1、Control.Invoke (同步调用)  

  (1)在主线程(UI线程)中调用Control.Invoke

    a、在主线程(UI线程)中调用Control.Invoke,先执行Invoke的方法,再执行Invoke后面的代码。

  (2)在子线程中调用Control.Invoke

    a、子线程中调用Control.Invoke,子线程将调用的方法封装成消息,调用API的RegisterWindowMessage()向UI窗口发送消息。 主线程继续向下执行,子线程处于阻塞状态。

    b、当该消息被主线程执行完成后,子线程才能继续往下执行。

2、Delegate.BeginInvoke (异步调用)

  (1)在主线程(UI线程)中调用Control.BeginInvoke

    a、在主线程(UI线程)中调用Control.BeginInvoke,将调用的方法封装成消息,调用API的RegisterWindowMessage()向UI窗口发送消息。先执行Invoke后面的代码,再执行Invoke的方法。

  (2)在子线程中调用Control.BeginInvoke

    a、子线程中调用Control.BeginInvoke,子线程将调用的方法封装成消息,调用API的RegisterWindowMessage()向UI窗口发送消息。主线程继续向下执行,子线程也继续向下执行。

    b、最后由主线程执行Invoke的方法。

3、Demo

  a、在主线程(UI线程)中调用Control.Invoke

复制代码

 1         private void btn_Main_Invoke_Click(object sender, EventArgs e)
 2         {
 3             //执行顺序:代码A -> 代码Invoke -> 代码B
 4             //都是在主线程中执行
 5 
 6             txt_Message.Text = "";
 7             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 8             //代码A
 9             this.txt_Message.Invoke(del);
10             //代码B
11             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
12         }

复制代码

  b、在子线程中调用Control.Invoke

复制代码

 1         private void btn_Sub_Invoke_Click(object sender, EventArgs e)
 2         {
 3             //执行顺序:
 4             //1、代码A(主线程执行)
 5             //2、代码D(主线程执行) 和 代码B(子线程执行) 并发执行
 6             //3、封装消息,并在UI线程中注册消息(子线程执行)
 7             //4、代码Invoke(主线程执行)
 8             //5、代码C(子线程执行)
 9 
10             txt_Message.Text = "";
11             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
12             //代码A
13             new Thread(() =>
14             {
15                 //代码B
16                 int temp = 0;
17                 this.txt_Message.Invoke(del);
18                 //代码C
19                 temp = 1;
20             }).Start();
21             //代码D
22             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
23         }

复制代码

  c、在主线程(UI线程)中调用Control.BeginInvoke

复制代码

 1         private void btn_Main_BeginInvoke_Click(object sender, EventArgs e)
 2         {
 3             //执行顺序:代码A -> 封装消息,并在UI线程中注册消息 -> 代码B -> 代码Invoke
 4             //都是在主线程中执行
 5 
 6             txt_Message.Text = "";
 7             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
 8             //代码A
 9             this.txt_Message.BeginInvoke(del);
10             //代码B
11             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
12         }

复制代码

  c、在子线程中调用Control.BeginInvoke

复制代码

 1         private void btn_Sub_BeginInvoke_Click(object sender, EventArgs e)
 2         {            
 3             //执行顺序:
 4             //1、代码A(主线程执行)
 5             //2、代码D(主线程执行) 和 代码B(子线程执行) 并发执行
 6             //3、封装消息,并在UI线程中注册消息(子线程执行)
 7             //4、代码C(子线程执行)
 8             //5、代码Invoke(主线程执行)
 9 
10             txt_Message.Text = "";
11             txt_Message.Text += "主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始工作\r\n";
12             //代码A
13             new Thread(() =>
14             {
15                 //代码B
16                 int temp = 0;
17                 this.txt_Message.BeginInvoke(del);
18                 //代码C
19                 temp = 1;
20             }).Start();
21             //代码D
22             txt_Message.Text += "\r\n主线程:" + Thread.CurrentThread.ManagedThreadId + "---开始结束\r\n";
23         }

复制代码

================================================================================================

invoke和begininvoke 区别

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

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

  1. control中的invoke、begininvoke。

  2. delegrate中的invoke、begininvoke。  

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

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

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

  根据这两个概念我们大致理解invoke表是同步、begininvoke表示异步。但是如何来进行同步和异步呢?我们来做一个测试。

invoke 例子:

 

复制代码

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 例子:

复制代码

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();


}
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的方法来解决。
普通的委托方法例如:

复制代码

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方法既可。我们还可以采用另一种委托的方式来实现,那就是匿名代理,例如:

复制代码

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)

 

{

 

    this.Invoke(new Action(()=>

 

    {

 

        button.Text="关闭";

 

    }));

 

}

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值