在我的问题代码中,这个异常是由于BeginInvoke()里面的参数填写不对导致的。
首先描述一下我的代码功能(当然我现在是特地为了验证这个bug而写的小例子,所以大家放心,代码量很小):
点击第“开始显示”按钮之后,就不停在listBox1中插入i = x并刷新界面显示,x一直从0到999,大概耗时4s左右。
主界面不停刷新listbox1的同时,我的子线程中需要进行一些耗时的操作,然后在主界面的listBox1上显示数据。
因为是在子线程中通过子类Class1发出消息eventShowData给主线程,然后主线程中的消息响应函数ShowAddData()里面调用lixtBox1,所以需要用BeginInvoke方法来操作listBox1(如果不用BeginInvoke方法的话,运行时会报“线程间操作无效: 从不是创建控件“listBox1”的线程访问它”的异常信息)
子类Class1代码如下:
using System.Threading;
namespace TestInvoke
{
public delegate void DelegateShowAddResult(bool bShow);
class Class1
{
public event DelegateShowAddResult eventShowData;
public void ShowData()
{
Thread.Sleep(2000);
eventShowData(true);
}
}
}
主界面代码如下:
using System;
using System.Threading;
using System.Windows.Forms;
namespace TestInvoke
{
public partial class Form1 : Form
{
Class1 myClass;
public Form1()
{
InitializeComponent();
myClass = new Class1();
myClass.eventShowData += ShowAddData;
}
private void ShowAddData(bool bShow)
{
if (listBox1.InvokeRequired)
{
listBox1.BeginInvoke(new DelegateShowAddResult(ShowAddData), bShow);
Console.WriteLine("run code after BeginInvoke");
}
else
{
if (bShow)
{
string str = "";
str = string.Format("ShowAddData:a+b = {0}", 100);
listBox1.Items.Insert(0, "childThread Insert data in listBox1");
}
}
}
private void button1_Click(object sender, EventArgs e)
{
string str = "";
for (int i = 0; i < 1000; i++)
{
str = string.Format("i = {0}", i);
listBox1.Items.Insert(0, str);
this.Refresh();
Thread.Sleep(1);
}
}
private void CallToChildThread()
{
myClass.ShowData();
}
private void button2_Click(object sender, EventArgs e)
{
Console.WriteLine("开启线程:");
ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();
}
}
}
上面这段代码已经是正常代码了,我之前犯错的时候就是在ShowAddData方法中的listBox1.BeginInvoke()这里面少加了,bShow这个参数,这个参数不加的话编译可以正常通过,但程序跑到这边的时候就会中断并报异常,异常界面如下:
这种情况下不会提示是在哪一行代码出的问题,所以在代码量很多的情况下,我真的是非常抓狂,因此特意在这里记录一下自己的坑!!!
在这里,我顺便记录一下BeginInvoke和Invoke方法的区别,官方的解释如下:
BeginInvoke:在创建控件的基础句柄所在线程上,用指定的参数异步执行指定委托。
Invoke:在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。
官方解释是不是看的一愣一愣的?这TM看得出毛的区别啊?
通过实际测试,我记录一下这两者的区别:
看下面两种写法:
方式1:
listBox1.BeginInvoke(new DelegateShowAddResult(ShowAddData), bShow);
Console.WriteLine("run code after BeginInvoke");
方式2:
listBox1.Invoke(new DelegateShowAddResult(ShowAddData), bShow);
Console.WriteLine("run code after BeginInvoke");
当代码执行到listBox1.BeginInvoke()的时候,
方式1中ShowAddData()方法中的代码和Console.WriteLine(“run code after BeginInvoke”);会同时运行
而方式2中会等到ShowAddData()方法中的代码运行结束之后,才会继续运行Console.WriteLine(“run code after BeginInvoke”);