多线程-Invoke

在多线程中免不了要有一些更新UI的操作,但在.net framework中是不允许直接在工作线程中直接操作Control,这在Jeffrey的核心编程中也有提及。

 

.net为在工作线程中更新UI提供了一种机制,那就是Invoke,在Control类中定义了一个Invoke方法,MSDN中Invoke的解释如下:

 在拥有此控件的基础窗口句柄的线程上执行委托。

 

此外Control还有一个InvokeRequired 属性,MSDN中的解释如下:

获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。

 

获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。

如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。

Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。


除了 InvokeRequired 属性以外,控件上还有以下四个线程安全的方法可供调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,当从另一个线程进行调用时,应使用这些 Invoke 方法之一。

 

以下是摘自一篇BLOG文章的内容:

 STA 模型要求需从控件的非创建线程调用的控件上的任何方法必须被封送到(在其上执行)该控件的创建线程。基类 Control 为此目的提供了若干方法(Invoke、BeginInvoke 和 EndInvoke)。Invoke 生成同步方法调用;BeginInvoke 生成异步方法调用。
    Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。

 

自此Invoke的作用就很明确了:

在工作线程中调用所要更新Control的Invoke方法,并传递一个委托实例,这个委托实例所指的方法中有一些更新Control的代码,而这些代码会封送给Control创建线程或者说拥有线程来执行,而不是由调用Invoke的工作线程来执行(如果Control不是工作线程创建的话)。

 

那么如果在UI线程中调用Control的Invoke方法会怎么样呢

  //定义委托 public delegate void DgeUpdateTxtDebug(int i); public partial class Form1 : Form { public Form1() { InitializeComponent(); } public void UpdateTxtDebug(int i) { txtDebug.Text = i.ToString(); Trace.WriteLine(Thread.CurrentThread.Name + "在执行更新"); } private void btnCreateThread_Click(object sender, EventArgs e) { Trace.WriteLine("开始测试"); Thread.CurrentThread.Name = "UI线程"; txtDebug.Invoke(new DgeUpdateTxtDebug(UpdateTxtDebug), new object[1] { 10 }); Trace.WriteLine("结束测试"); } } 

 执行后得到的Trace的输入是"UI线程在执行更新",这也相MSDN中关于Invoke的描述相符合,因为txtDebug的创建线程就是UI线程本身。此外Trace的输入顺序是:

开始测试

UI线程在执行更新

结束测试

如果将txtDebug.Invoke的调用换成txtDebug.BeginInvoke(....),那么执行顺序就可能是

开始测试

结束测试

UI线程在执行更新

也可能是其它顺序,这不确定,这个小改动也说明了Invoke的另一个特性:

Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。

Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。

 

在UI线程中调用Invoke并没有什么意义,因为Invoke .net提供给工作线程更新UI的,所以下面的示例代码真正说明了Invoke的作用:

//定义委托 public delegate void DgeUpdateTxtDebug(int i); public partial class Form1 : Form { public Form1() { InitializeComponent(); } //更新UI public void UpdateTxtDebug(int i) { txtDebug.Text = i.ToString(); Trace.WriteLine(Thread.CurrentThread.Name + "在执行更新"); } //线程方法 public void TProc(object obj) { Control ctl = (Control)obj; Trace.WriteLine("线程启动 "); for (int i = 0; i < 100; i++) { Thread.Sleep(100); if (ctl.InvokeRequired) ctl.BeginInvoke(new DgeUpdateTxtDebug(UpdateTxtDebug), new object[1] { i + 1 }); else UpdateTxtDebug(i); Trace.WriteLine(Thread.CurrentThread.Name + "在执行计算"); } } //开始测试 private void btnCreateThread_Click(object sender, EventArgs e) { Thread.CurrentThread.Name = "UI线程"; Thread t = new Thread(new ParameterizedThreadStart(TProc)); t.Start(txtDebug); //由UI线程再来调用一次 //TProc(txtDebug); //DoWork w = new DoWork(txtDebug, new UpdateTxtDebug(UpdateTxtDebug)); //Thread t = new Thread(new ThreadStart(w.TProc)); //t.Name = "工作线程"; //t.Start(); } }

 注意在线程方法中的两种不同情况的输入:i+1 和 i 这个小区别是为了演示InvokeRequired属性的作用,

在使用工作线程的情况下,txtDebug的Text最终会是100,而如果把//由UI线程再来调用一次下面一行的代码注释掉,则Text就会是99

 

上面这个示例代码会让人感觉很混乱,所有东西都挤在一起,让人感觉有点混乱,通常我们作一个线程辅助类,放在一个单独的.cs文件中,而UI更新方法自然还是放在Form1类中(好像没有什么方法可以把它加到txtDebug的定义里),UI更新方法的委托定义放在一个公共的地方,比如新建立一个Delegate.css,在参数传递方面,使用多线程-参数传递中提及的方法,通过在线程辅助类中定义一个Control 私有成员和一个UI更新委托的引用。在类构造中初始化它们的方式来解决。

 

以下是改进后的代码范例:

 

首先建立一个Delegate.css专门来放委托

 using System; using System.Collections.Generic; using System.Text; namespace MultiThread { public delegate void UpdateTxtDebug(int i); }

 

建立DoWork.css存放线程辅助类的定义

using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Threading; using System.Windows.Forms; namespace MultiThread { //线程辅助类 public class DoWork { private Control uiCtl; private UpdateTxtDebug uiDge; public DoWork(Control uiCtl, UpdateTxtDebug uiDge) { this.uiCtl = uiCtl; this.uiDge = uiDge; } public void TProc() { Trace.WriteLine("线程启动 "); for (int i=0; i < 100; i++) { Thread.Sleep(100); if (uiCtl.InvokeRequired) uiCtl.BeginInvoke(uiDge, new object[1] { i + 1 }); else uiDge(i); Trace.WriteLine(Thread.CurrentThread.Name + "在执行计算"); } } } }

 

  Form1.css的定义如下

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; using System.Diagnostics; namespace MultiThread { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //更新UI public void UpdateTxtDebug(int i) { txtDebug.Text = i.ToString(); Trace.WriteLine(Thread.CurrentThread.Name + "在执行更新"); } //开始测试 private void btnCreateThread_Click(object sender, EventArgs e) { Thread.CurrentThread.Name = "UI线程"; DoWork w = new DoWork(txtDebug, new UpdateTxtDebug(UpdateTxtDebug)); Thread t = new Thread(new ThreadStart(w.TProc)); t.Name = "工作线程"; t.Start(); //由UI线程再调用一次 w.TProc(); } } }

 

BackgroundWorker封装了BeginInvoke并进行了扩展

关于EndInvoke的使用,请参考:

C#线程系列讲座(1):BeginInvoke和EndInvoke方法

http://www.360doc.com/content/080820/17/55253_1559793.html

 

转载于:https://www.cnblogs.com/x_craft/archive/2009/07/13/3606537.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值