在查询海量数据时,由于查询需要时间,因此要在查询的同时显示状态信息以通知用户当前正在进行的操作,通常采用多线程进行处理,但是初学者常常使用如下类似的代码:
private void buttonBookStat_Click( object sender, EventArgs e)
{
ThreadStart tds1 = new ThreadStart(prompt);
Thread td1 = new Thread(tds1);
td1.Priority = ThreadPriority.Normal;
td1.Start();
ThreadStart tds2 = new ThreadStart(showBooksInfo);
Thread td2 = new Thread(tds2);
td2.Priority = ThreadPriority.Normal;
td2.Start();
}
// 显示提示
private void prompt()
{
this .labelPrompt.Text = " 正在查询,请稍侯...... " ;
}
// 查询并绑定到窗体控件datagridview1
private void showBooksInfo()
{
string sql = " select ......... " ;
try
{
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
DataSet ds = new DataSet();
da.Fill(ds, " table1 " );
this .dataGridView1.AutoGenerateColumns = true ;
this .dataGridView1.DataSource = ds;
this .dataGridView1.DataMember = " table1 " ;
this .labelPrompt.Text = " 查询完毕 " ;
}
catch (SqlException er)
{
MessageBox.Show(er.Message);
}
}
这样的代码在子线程中操作窗体控件,盖茨大叔说是不安全的,但我用VS2005编译并不报错,也能正常运行,不过,运行时还是出了问题,在上述例子中datagridview控件的滚动条不能正常显示,无法使用滚动条滚动显示数据,看来,如果要在子线程中操作窗体控件,还是得采用线程安全调用的方式。要使用安全调用的方式,必须先定义一个委托,其签名与我们要操作控件的函数相同,步骤如下:
1.定义一个与窗体控件操作方法签名相同的委托
2.定义操作窗体控件的方法
3.定义一个安全调用方法,用2中的方法实例化我们定义的委托
4.在线程中调用3中的安全方法
因此,我们将上述代码作一修改,红色为新增和改动代码
//定义委托
delegate void mydelegate();
//button按钮的click事件处理函数
private void buttonBookStat_Click(object sender, EventArgs e)
{
ThreadStart tds1 = new ThreadStart(promptSafe);
Thread td1 = new Thread(tds1);
td1.Priority = ThreadPriority.Normal;
td1.Start();
ThreadStart tds2 = new ThreadStart(showBooksInfoSafe);
Thread td2 = new Thread(tds2);
td2.Priority = ThreadPriority.Normal;
td2.Start();
}
//显示提示
private void prompt()
{
this.labelPrompt.Text = "正在查询,请稍侯......";
}
//安全调用显示提示方法
private void promptSafe()
{
if (this.labelPrompt.InvokeRequired)
{
mydelegate showprompt = new mydelegate(prompt);
this.Invoke(showprompt);
}
else
{
prompt();
}
}
//查询并绑定到窗体控件datagridview1
private void showBooksInfo()
{
string sql = "select .........";
try
{
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
DataSet ds = new DataSet();
da.Fill(ds, "table1");
this.dataGridView1.AutoGenerateColumns = true;
this.dataGridView1.DataSource = ds;
this.dataGridView1.DataMember = "table1";
this.labelPrompt.Text = "查询完毕";
}
catch (SqlException er)
{
MessageBox.Show(er.Message);
}
}
//安全调用查询
private void showBooksInfoSafe()
{
if (this.dataGridView1.InvokeRequired)
{
//定义委托对象
mydelegate showbooks = new mydelegate(showBooksInfo);
//因此线程占用大量CPU时间,因此让它分些CPU时间出来给其他线程
Thread.Sleep(10);
//通过窗体的Invoke方法调用委托
this.Invoke(showbooks);
}
else
{
showBooksInfo();
}
}
再次编译运行,现在你会发现datagridview控件的滚动条又正常了,这就是安全调用的好处,它会解决一些莫名其妙出现的问题。
不过,我们解决了问题之后仍然存在疑问:到底这种不安全调用的方式问题出在哪?其底层的错误在什么地方?在上述具体问题中,不安全调用方式仍然可以给datagridview指定数据源并显示,但在滚动条那里出现问题,我们无法获知datagridview这个控件的底层实现方式,微软也只是笼统地说在线程中操作窗体控件需要安全方式