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