C#解决窗体冷却的invoke用例
主要参考
在做GUI编程时,如果某次动作的运算时间非常长,比如要对大量数据进行加载,或者执行一个运算量很大的计算,或是socket编程在网络速度不理想的时候,窗体会停住无法响应,直到加载完成或运算结束,这对GUI操作来说是不太合适的。比如从外部相机设备读取图像数据时,图片有2048*2048像素*2字节的大小,并且要连续进行加载,虽然图像还在显示,但是整个程序都被加载图像占用,其它的控件几乎无法正常使用。最近在做的一个程序在调用外部批处理,批处理的执行结果显示在程序界面。自批处理调用之始到批处理进行完,程序都会处在无法操作的假死状态。
为了解决这个问题,就要掌握C#中invoke和delegate的特性。
先看主要过程
C#
x
14
14
1
private void button1_Click(object sender, EventArgs e)
2
{
3
richTextBox3.Text = "running..,";
4
//开始执行批处理,这里往往耗时较长
5
String output = "";
6
RunCmd("test.bat", out output);
7
String[] res = output.Split('\n');
8
//批处理执行完毕, 将内容更新到richTextBox3
9
richTextBox3.Text = "";
10
for (int i = 4; i < res.Length; i++)
11
{
12
this.richTextBox3.Text += res[i];
13
}
14
}
点击button1之后,先让文本框显示”running…”的文字,然后去执行批处理,这时窗体会冷却直到运行完成。这是线程之间出现占用现象,我们启动一个新的线程进行这些操作
C#
1
17
17
1
private void button1_Click(object sender, EventArgs e)
2
{
3
richTextBox3.Text = "compiling...";
4
new Thread(
5
() =>
6
{
7
String output = "";
8
RunCmd("tst.bat", out output);
9
String[] res = output.Split('\n');
10
richTextBox3.Text = "";
11
for (int i = 4; i < res.Length; i++)
12
{
13
this.richTextBox3.Text += res[i];
14
}
15
}
16
).Start();
17
}
可以发现,点击button1之后窗体不再冷却,是可以对其它控件进行操作的。但是突然跳出来 “线程间操作无效: 从不是创建控件“richTextBox3”的线程访问它。”的异常。查阅相关材料发现,是因为官方认为是当有多个并发线程尝试对UI进行读写时,容易造成线程争用资源带来的死锁。所以,默认不允许以非UI线程访问控件。
但很多情况下我们又确实需要用异步线程对UI进行一些操作。这时就用到Control.Invoke和Control.BeginInvoke。我们用Action<>把原来的语句包装成lambda函数,需要的时候就用invoke调这个lambda函数,就可以解决“不是创建控件*的线程访问”的异常。修改如下:
C#
1
21
21
1
private void button1_Click(object sender, EventArgs e)
2
{
3
richTextBox3.Text = "compiling...";
4
new Thread(
5
() =>
6
{
7
String output = "";
8
RunCmd("tst.bat", out output);
9
String[] res = output.Split('\n');
10
Action<int> setNullForRT3 = (data) => richTextBox3.Text = "";
11
Invoke(setNullForRT3, 0);
12
for (int i = 4; i < res.Length; i++)
13
{
14
Action<int> act1 = (data) =>
15
{
16
this.richTextBox3.Text += res[i];
17
};
18
Invoke(act1, i);
19
}
20
}).Start();
21
}