InvokeRequired and Invoke
http://msdn.microsoft.com/zh-cn/library/system.windows.forms.control.invokerequired(v=VS.90).aspx
在.net中,控件的访问更新只能在"拥有"这个控件的线程上执行,否则回抛异常。
MS在Control类上提供了一个InvokeRequired的属性。下面是MSDN对这个属性的一个注释。我这里只有中文版的。呜呜。
Control.InvokeRequired 属性
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。
我查过英文版的注释,无论是中文的还是英文的,都强调了一点,就是说控件只能在创建此控件的线程上才能被调用。但是实际上却不是这样子的。
正确地说,这里有两个概念,即创建控件的线程和拥有控件的线程。关于"拥有"这个词,这里只是我自己YY出来的,但愿能说明问题,后面还有更多的解释。
创建控件的线程很好理解,那什么是拥有控件的线程呢。两者是一样的吗?不一样。
拥有控件的线程。我这里指的应该是创建了此控件handle的线程,而不是创建控件本身的那个线程。两者可以是相同的,也可以是不同的。
那线程怎么样创建一个控件的handle呢?很简单,我们还是先看看MSDN对Handle的说明。
Control.Handle 属性
获取控件绑定到的窗口句柄。
备注
Handle 属性的值是 Windows HWND。如果句柄尚未创建,引用该属性将强制创建句柄。
在实际中,当一个控件被创建出来后,它的Handle是还没有被创建的,只有当第一次引用了该发生之后,才会被创建。
下面是我写的一段代码。
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;
namespace InvokeRequiredDemo
{
public partial class Form1 : Form
{
Control controlCreatedFromOtherThread;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (null == controlCreatedFromOtherThread)
{
Thread.Sleep(100);
}
MessageBox.Show(controlCreatedFromOtherThread.InvokeRequired.ToString());
}
private void CreateControlInstance()
{
Control newControlInstance = new Control();
controlCreatedFromOtherThread = newControlInstance;
}
}
}
运行这段代码,对话框抛出的是false。说明主线程是可以调用更新控件的,虽然这个控件是在另一个线程中被创建。
下面我们改一下CreateControlInstance()这个方法。
private void CreateControlInstance()
{
Control newControlInstance = new Control();
IntPtr intPtr = newControlInstance.Handle;
controlCreatedFromOtherThread = newControlInstance;
}
我们在代码中加多了一名IntPtr intPtr = newControlInstance.Handle;这句代码将让创建控件的线程(不是主线程)去访问控件的Handle,代码更改后运行,对话框抛出的是true。
关于Control.Handle,还有另一个有趣的东东。比如上面的代码中,你让创建控件的线程同时创建它的Handle,再回到主线程中,这时候,你试着访问它的Handle,会发生什么呢?
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (null == controlCreatedFromOtherThread)
{
Thread.Sleep(100);
}
MessageBox.Show(controlCreatedFromOtherThread.Handle.ToString());
}
答案是抛异常。线程间操作无效: 从不是创建控件“”的线程访问它。
再改改代码:
public delegate void SampleDelegate();
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (null == controlCreatedFromOtherThread)
{
Thread.Sleep(100);
}
SampleDelegate sampleDelegate = new SampleDelegate(SampleMethod);
controlCreatedFromOtherThread.Invoke(
sampleDelegate);
}
public void SampleMethod()
{
// Nothing to do
// Please try to make a breakpoint here.
// In this case, no stack here when run the demo.
}
运行,你会发现,代码根本没有走到SampleMethod()里面去,很奇怪,我在公司时试的结果,是直接在controlCreatedFromOtherThread.Invoke上抛异常的(NullReference,但是我们知道,controlCreatedFromOtherThread并不是空的)。可能是framework或是IDE不一样。公司是VS2005?C#EXPRESS?家里是VS2008。
把controlCreatedFromOtherThread.Invoke改成this.Invoke,SampleMethod()就可以走进去了。
我猜测应该是在用Invoke时,是会需要根据Handle来找到拥有控件的线程,然后在这个线程里调用代理的方法,上面的代码因为主线程拿不到Handle,所以为null,故抛了异常。
最后,再来说说这个Handle是什么时候会被创建。MSDN上说被引用的时候会创建,那什么时候是会被引用呢?直接Control.Handle,当然是,除此之外,还有其它一些case。
比如把控件加到窗体上去,这个控件的Handle也会被窗体所在的线程所创建。事实上,如果控件和窗体的Handle不是在同一样线程上,你是无法把它加到窗体上去的。比如:
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (null == controlCreatedFromOtherThread)
{
Thread.Sleep(100);
}
this.Controls.Add(controlCreatedFromOtherThread);
}
我VS2008中,会抛出“线程间操作无效: 从不是创建控件“”的线程访问它。”的异常。在公司时抛出的异常更加详细,大概就是说控件不能被加到窗体中,因为他们的Handle不是在同一个纯种上。
再看下面的例子:
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (null == controlCreatedFromOtherThread)
{
Thread.Sleep(100);
}
this.Controls.Add(controlCreatedFromOtherThread);
}
private void CreateControlInstance()
{
Control newControlInstance = new Control();
Form newForm = new Form();
newForm.Controls.Add(newControlInstance);
controlCreatedFromOtherThread = newControlInstance;
}
注意这里在CreateControlInstance中又创建了一个窗体,并把新创建的控件加到窗体上面去,但是实际上,这个时候控件的Handle还是没有被创建出来,我们运行代码,是不会有异常的。之所以这个,是因为这个新的窗体还没有被Show出来。
再改改:
private void CreateControlInstance()
{
Control newControlInstance = new Control();
Form newForm = new Form();
newForm.Controls.Add(newControlInstance);
newForm.Show();
controlCreatedFromOtherThread = newControlInstance;
}
加了newForm.Show(),再运行,异常就出来了。说明新控件的Handle随着窗体的Show也一起被创建。Show方法应该是会先创建窗体的Handle,再创建里面子控件的Handle。
最后补充一点,其实在同一个进程中的不同线程是共享同一堆的,也就是可以共享一片内存。但是为什么不同的线程不能共享控件的Handle呢?我估计是MS特意这样子做,因为如果Handle被共享,在多线程的环境中很容易会出现死锁。
Control controlCreatedFromOtherThread;
IntPtr controlIntPtrFromOtherThread;
private void Form1_Load(object sender, EventArgs e)
{
Thread createControlThread = new Thread(
new ThreadStart(CreateControlInstance));
createControlThread.Start();
while (IntPtr.Zero.Equals(controlIntPtrFromOtherThread))
{
Thread.Sleep(100);
}
controlCreatedFromOtherThread = Control.FromHandle(controlIntPtrFromOtherThread);
MessageBox.Show(controlCreatedFromOtherThread.Name);
}
private void CreateControlInstance()
{
Control newControlInstance = new Control();
newControlInstance.Name = "hello, leland";
controlIntPtrFromOtherThread = newControlInstance.Handle;
}
这个例子中,在非主线程中创建了控件和Handle,并把Handle保存在全局变量中,回到主线程,还是可以根据Handle得到这个控件。
不过,很诡异的事情发生了。上面是把"hello,leland"给了控件的Name属性,在主线程的对话框中,抛出来的还是"hello,leland",this is correct. 我试着把"hello,leland"给Text属性,然而,抛出的是一个空的字符串。。。。
这个例子只是说明不同的线程可以共享一个堆。。关于死锁的例子,就不说了。哈哈。
http://www.360doc.com/content/11/0330/18/6255786_105939099.shtml
InvokeRequired and Invoke相关推荐
- c# InvokeRequired和Invoke
C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它.此时它将会在内部调用ne ...
- c# winform InvokeRequired 解决跨线程访问控件
C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它. Windows 窗体中 ...
- C# 多线程修改控件时遇到:创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke
一般在多线程调用UI控件时,涉及到跨线程修改UI,需要使用委托,比如如下: this.Invoke((MethodInvoker)delegate{btnRefresh.Enabled = true; ...
- C# async await 学习笔记2
C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html) 提到了ThreadId是一样的,突然想到在WinForm中,非UI线程 ...
- [转载]分享WCF聊天程序--WCFChat
http://www.cnblogs.com/gaoweipeng/archive/2009/09/04/1560260.html 无意中在一个国外的站点下到了一个利用WCF实现聊天的程序,作者是:N ...
- [转]WinForm下Splash(启动画面)制作
本文转自:http://www.smartgz.com/blog/Article/1088.asp 原文如下: 本代码可以依据主程序加载进度来显示Splash. static class Pr ...
- C#做的在线升级小程序
转自原文C#做的在线升级小程序 日前收到一个小任务,要做一个通用的在线升级程序.更新的内容包括一些dll或exe或.配置文件.升级的大致流程是这样的,从服务器获取一个更新的配置文件,经过核对后如有新的 ...
- c#中对Cross-thread operation not valid错误的处理办法
目录 目录 概要 1. Example 1.1 Unsafe access to control 1.2 What's mean? 2. The first choice : CheckForIlle ...
- C# 实现基于ffmpeg加虹软的人脸识别
2019独角兽企业重金招聘Python工程师标准>>> 关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人 ...
最新文章
- [MySQL] 几句MySQL时间筛选SQL语句[进入查看]
- 解决Eclipse查看源代码出现Source not found的问题
- android快速打包工具下载,【Android】多渠道打包,其实可以更快
- QML笔记-键盘事件中同时响应onDigitXXPressed与onPressed
- Linux多线程同步
- mysql 快速初始化_MySQL中的批量初始化数据的对比测试(r12笔记第71天)
- 单片机shell命令_nr_micro_shell
- 实验5.3 编程实现两字符串的连接(使用字符数组)
- 程序员修炼之道:从小工到专家
- python requests text content_对python requests的content和text方法的区别详解
- TI DSP处理器中CMD 文件的那些事儿
- PF_PACKET说开去
- TF-IDF算法总结
- 记一次学习爬取豆瓣数据于Excel表的爬虫
- Unity表情聊天(NGUI图文混排)
- 淘宝原数据商品详情API调用示例
- 新农慕课python小测验答案_python面向对象程序设计_章节测验,期末考试,慕课答案查询公众号...
- Jenkins邮箱配置中,使用SSL连接的问题
- 号)、sex(性别)、birthday(出生日期)、id(身份证号)等等。其中“出生日期”定义为一个“日期”类内嵌子对象。用成员函数实现对人员信息的录入和显示。要求包括:构造函数和析构函数、拷贝构造函
- 在线扫描php后门_webshell后门扫描-PHP版