InvokeRequired and Invoke默认分类 2010-10-19 16:34:17 阅读27 评论0 字号:大中小 订阅 在.net中,控件的访问更新只能在"拥有"这个控件的线程上执行,否则回抛异常。 MS在Control类上提供了一个InvokeRequired的属性。下面是MSDN对这个属性的一个注释。我这里只有中文版的。呜呜。 Control.InvokeRequired 属性获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。 我查过英文版的注释,无论是中文的还是英文的,都强调了一点,就是说控件只能在创建此控件的线程上才能被调用。但是实际上却不是这样子的。 正确地说,这里有两个概念,即创建控件的线程和拥有控件的线程。关于"拥有"这个词,这里只是我自己YY出来的,但愿能说明问题,后面还有更多的解释。 创建控件的线程很好理解,那什么是拥有控件的线程呢。两者是一样的吗?不一样。 拥有控件的线程。我这里指的应该是创建了此控件handle的线程,而不是创建控件本身的那个线程。两者可以是相同的,也可以是不同的。 那线程怎么样创建一个控件的handle呢?很简单,我们还是先看看MSDN对Handle的说明。 Control.Handle 属性 获取控件绑定到的窗口句柄。备注 在实际中,当一个控件被创建出来后,它的Handle是还没有被创建的,只有当第一次引用了该发生之后,才会被创建。 下面是我写的一段代码。 using System; namespace InvokeRequiredDemo public Form1() private void Form1_Load(object sender, EventArgs e) while (null == controlCreatedFromOtherThread) MessageBox.Show(controlCreatedFromOtherThread.InvokeRequired.ToString()); private void CreateControlInstance() 运行这段代码,对话框抛出的是false。说明主线程是可以调用更新控件的,虽然这个控件是在另一个线程中被创建。 下面我们改一下CreateControlInstance()这个方法。 private void CreateControlInstance() 我们在代码中加多了一名IntPtr intPtr = newControlInstance.Handle;这句代码将让创建控件的线程(不是主线程)去访问控件的Handle,代码更改后运行,对话框抛出的是true。 关于Control.Handle,还有另一个有趣的东东。比如上面的代码中,你让创建控件的线程同时创建它的Handle,再回到主线程中,这时候,你试着访问它的Handle,会发生什么呢? private void Form1_Load(object sender, EventArgs e) while (null == controlCreatedFromOtherThread) 答案是抛异常。线程间操作无效: 从不是创建控件“”的线程访问它。 再改改代码: public delegate void SampleDelegate(); private void Form1_Load(object sender, EventArgs e) while (null == controlCreatedFromOtherThread) SampleDelegate sampleDelegate = new SampleDelegate(SampleMethod); 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) while (null == controlCreatedFromOtherThread) this.Controls.Add(controlCreatedFromOtherThread); 我VS2008中,会抛出“线程间操作无效: 从不是创建控件“”的线程访问它。”的异常。在公司时抛出的异常更加详细,大概就是说控件不能被加到窗体中,因为他们的Handle不是在同一个纯种上。 再看下面的例子: private void Form1_Load(object sender, EventArgs e) while (null == controlCreatedFromOtherThread) this.Controls.Add(controlCreatedFromOtherThread); private void CreateControlInstance() Form newForm = new Form(); controlCreatedFromOtherThread = newControlInstance; 注意这里在CreateControlInstance中又创建了一个窗体,并把新创建的控件加到窗体上面去,但是实际上,这个时候控件的Handle还是没有被创建出来,我们运行代码,是不会有异常的。之所以这个,是因为这个新的窗体还没有被Show出来。 再改改: private void CreateControlInstance() Form newForm = new Form(); controlCreatedFromOtherThread = newControlInstance; 加了newForm.Show(),再运行,异常就出来了。说明新控件的Handle随着窗体的Show也一起被创建。Show方法应该是会先创建窗体的Handle,再创建里面子控件的Handle。 最后补充一点,其实在同一个进程中的不同线程是共享同一堆的,也就是可以共享一片内存。但是为什么不同的线程不能共享控件的Handle呢?我估计是MS特意这样子做,因为如果Handle被共享,在多线程的环境中很容易会出现死锁。 Control controlCreatedFromOtherThread; private void Form1_Load(object sender, EventArgs e) while (IntPtr.Zero.Equals(controlIntPtrFromOtherThread)) controlCreatedFromOtherThread = Control.FromHandle(controlIntPtrFromOtherThread); private void CreateControlInstance() 这个例子中,在非主线程中创建了控件和Handle,并把Handle保存在全局变量中,回到主线程,还是可以根据Handle得到这个控件。 不过,很诡异的事情发生了。上面是把"hello,leland"给了控件的Name属性,在主线程的对话框中,抛出来的还是"hello,leland",this is correct. 我试着把"hello,leland"给Text属性,然而,抛出的是一个空的字符串。。。。 这个例子只是说明不同的线程可以共享一个堆。。关于死锁的例子,就不说了。哈哈。 |
|