Control的Invoke和BeginInvoke
Control 的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行的。也就是说如果你的委托方法用来取花费时间长的数据,然后更新界面什么 的,千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死。
为了这个我还专门写了代码实验了一把,果然如此,不管是Invoke还是BeginInvoke都会造成界面假死,说明线程被阻塞了。
就是在我们跨线程修改UI界面的时候,在非控件创建线程中操作控件这个是不安全的,代码在执行的时候是会报错的。
我们只能把要调用的方法封装为委托,然后通过BeginInvoke这个方法来执行。(调用的方法)
private void button1_Click(object sender, EventArgs e) { var task = Task.Factory.StartNew(() => { for (int i = 0; i < 9999; i++) { AddItemIntoComBox(i); Thread.Sleep(1000); } }); } void AddItemIntoComBox(int index) { if (comboBox1.InvokeRequired) comboBox1.BeginInvoke(new Action<int>(AddItemIntoComBox), new object[] { index }); else comboBox1.Items.Add("测试" + index); }
代码还是很容易理解的。启动一个新的线程,线程里面循环999次,每次干的活都一样,就是调用AddItemIntoComBox这个方法,并把循环次数给了这个方法
执行完这个方法后睡一秒。
AddItemIntoComBox 这个方法就更简单了。判断是否在UI线程中,如果不是进行一个委托调用。 给combox里面添加一个值。
让我在看另一段代码:
private void button1_Click(object sender, EventArgs e) { var task = Task.Factory.StartNew(AddItemIntoComBox); } void AddItemIntoComBox() { if (comboBox1.InvokeRequired) { comboBox1.BeginInvoke(new Action(AddItemIntoComBox)); } else { for (int i = 0; i < 999; i++) { comboBox1.Items.Add("测试" + i); Thread.Sleep(1000); } } }
差别不是很大,只是循环的位置变了。
但影响却很大,第二种写法会造成主线程阻塞。原因很简单:
跨线程更新UI界面不是真正的跨线程,而是将方法作为一个委托(指针函数)封装到了主线程。说的简单点其实还是在主线程中操作。
浙公网安备 33010602011771号