多线程与UI操作(二)
为了让程序尽快响应用户操作,在开发Windows应用程序时经常会使用到线程。对于耗时的操作如果不使用线程将会是UI界面长时间处于停滞状态,这种情况是用户非常不愿意看到的,在这种情况下我们希望使用线程来解决这个问题。
下面是一个使用多线程操作界面UI的代码:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- namespace ThreadPoolDemo
- {
- public partial class ThreadForm : Form
- {
- public ThreadForm()
- {
- InitializeComponent();
- }
- private void btnThread_Click(object sender, EventArgs e)
- {
- Thread thread = new Thread(new ThreadStart(Run));
- thread.Start();
- }
- private void Run()
- {
- while (progressBar.Value < progressBar.Maximum)
- {
- progressBar.PerformStep();
- }
- }
- }
- }
我们的本意是点击“启动”按钮来启动模拟一个操作,在进度条中显示操作的总体进度。不过如果我们真的点击“启动”按钮会很失望,因为它会抛出一个System.InvalidOperationException异常,异常描述就是“线程间操作无效: 从不是创建控件‘progressBar’的线程访问它。”
CheckForIllegalCrossThreadCalls属性
之所以会出现这样的情况是因为在.NET中做了限制,不允许在调试环境下使用线程访问并非它自己创建的UI控件,这么做可能是怕在多线程环境下对界面控件进行操作会出现不可预知的情况,如果开发者可以确认自己的代码操作界面不会出现问题,可以用比较简单的方法解决,那就是设置CheckForIllegalCrossThreadCalls这个静态属性,它默认是true,如果将其设为false的话,以后在多线程环境下操作界面也不会抛出异常了,我们上面的代码可以修改为:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- namespace ThreadPoolDemo
- {
- public partial class ThreadForm : Form
- {
- public ThreadForm()
- {
- InitializeComponent();
- }
- private void btnThread_Click(object sender, EventArgs e)
- {
- //指示是否对错误线程的调用,即是否允许在创建UI的线程之外访问线程
- CheckForIllegalCrossThreadCalls = false;
- Thread thread = new Thread(new ThreadStart(Run));
- thread.Start();
- }
- private void Run()
- {
- while (progressBar.Value < progressBar.Maximum)
- {
- progressBar.PerformStep();
- }
- }
- }
- }
这样再执行程序就不会抛出异常了。
不过使用上面的代码我们可能还有些犯嘀咕,毕竟是不允许直接在线程中直接操作界面的,那么我们还可以用Invoke方法。
Invoke方法来操作界面
下面是一个例子:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- namespace ThreadPoolDemo
- {
- public partial class ThreadForm : Form
- {
- //定义delegate以便Invoke时使用
- private delegate void SetProgressBarValue(int value);
- public ThreadForm()
- {
- InitializeComponent();
- }
- private void btnThread_Click(object sender, EventArgs e)
- {
- progressBar.Value = 0;
- //指示是否对错误线程的调用,即是否允许在创建UI的线程之外访问线程
- //CheckForIllegalCrossThreadCalls = false;
- Thread thread = new Thread(new ThreadStart(Run));
- thread.Start();
- }
- //使用线程来直接设置进度条
- private void Run()
- {
- while (progressBar.Value < progressBar.Maximum)
- {
- progressBar.PerformStep();
- }
- }
- private void btnInvoke_Click(object sender, EventArgs e)
- {
- progressBar.Value = 0;
- Thread thread = new Thread(new ThreadStart(RunWithInvoke));
- thread.Start();
- }
- //使用Invoke方法来设置进度条
- private void RunWithInvoke()
- {
- int value = progressBar.Value;
- while (value< progressBar.Maximum)
- {
- //如果是跨线程调用
- if (InvokeRequired)
- {
- this.Invoke(new SetProgressBarValue(SetProgressValue), value++);
- }
- else
- {
- progressBar.Value = ++value;
- }
- }
- }
- //跟SetProgressBarValue委托相匹配的方法
- private void SetProgressValue(int value)
- {
- progressBar.Value = value;
- }
- }
- }
这个方法的功能跟上面的操作是一样的,只不过不需要设置CheckForIllegalCrossThreadCalls属性,而且还不会抛出异常。