咱们先来看一段运行时会抛出 InvalidOperationException 异常的代码段:安全
private void btnThreadA_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ChangeTextBox);
thread.IsBackground = true;
thread.Start();
}
void ChangeTextBox()
{
for (int i = 0; i < 10000; i++)
{
int num = Int32.Parse(txtNum.Text);
num++;
txtNum.Text = num.ToString();
}
}
微软在子线程修改 UI 线程的控件值时给出的安全限制方案为:在 VS2005 或者更高版本中,只要不是在控件的建立线程(通常就是指UI主线程)上访问控件的属性就会抛出这个错误,解决方法就是利用控件提供的 Invoke 和 BeginInvoke 把调用封送回 UI 线程,也就是让控件属性修改在UI线程上执行;或者 禁用此安全限制。多线程
解决方案一:解除该控件上对错误线程调用的检查(谨慎使用)。app
public Form2()
{
InitializeComponent();
// 解除 TextBox 对错误线程调用的检查
// 若是要捕获了对错误线程的调用,则为 true(默认值);不然为 false
// 对控件权限能够开放的更大 例如 Control、Form 等
TextBox.CheckForIllegalCrossThreadCalls = false;
}
解决方案二:异步
void ChangeTextBox(string str)
{
txtNum.Text = str;
}
// 增长一个委托
delegate void ChangeTextBoxEventHandler(string str);
// 次循环必须在子线程上运行,而后将最新值传递到 UI 线程 文本框才会即时变化
// 若是循环写在 ChangeTextBox 函数中,那么循环真实运行权会交由 UI 线程,你只会直接看见结果,看不到过程
void ThreadRun1()
{
for (int i = 0; i < 10000; i++)
{
int num = int.Parse(txtNum.Text);
num++;
// 使用 Invoke 方法,将函数运行劝交回给 UI 线程
this.Invoke(new ChangeTextBoxEventHandler(ChangeTextBox), num.ToString());
}
}
private void btnThreadB_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ThreadRun1);
thread.IsBackground = true;
thread.Start();
}
这样已经可以达到效果,但不是微软案例推荐的写法。考虑到 ChangeTextBox 方法除了被子线程调用外,也可能被程序其它部分调用。所以,再次修改代码以下:函数
void ChangeTextBox(string str)
{
// InvokeRequired 值判断当前修改文本框的请求是否有必要交由 UI 线程来完成
// 若是为 Ture,说明次访问控件的行为来自子线程,则调用 Invoke 方法将代码执行权交给 UI 线程
// 注意,下面实质上是进行了一次方法回调自身的行为,区别在于再次调用自身时,已是 UI 线程在执行了
if (this.InvokeRequired)
{
this.Invoke(new ChangeTextBoxEventHandler(ChangeTextBox), str);
}
else
{
txtNum.Text = str;
}
}
// 增长一个委托
delegate void ChangeTextBoxEventHandler(string str);
// 次循环必须在子线程上运行,而后将最新值传递到 UI 线程 文本框才会即时变化
// 若是循环写在 ChangeTextBox 函数中,那么循环真实运行权会交由 UI 线程,你只会直接看见结果,看不到过程
void ThreadRun1()
{
for (int i = 0; i < 10000; i++)
{
int num = int.Parse(txtNum.Text);
num++;
ChangeTextBox(num.ToString());
}
}
private void btnThreadB_Click(object sender, EventArgs e)
{
Thread thread = new Thread(ThreadRun1);
thread.IsBackground = true;
thread.Start();
}
与 Invoke 方法相对应的还有 BeginInvoke ()、EndInvoke () 这些异步方法。不管是同步仍是异步,这些方法老是会经过代理从新回到 UI 线程上执行。ui
这些方法向 UI 线程的消息队列中放入一个消息,当 UI 线程处理这个消息时,就会在本身的上下文中执行传入的方法。换句话说,凡是使用 BeginInvoke 和 Invoke 调用的线程都是在UI主线程中执行的,因此即便这些方法里涉及到一些静态变量,也不用考虑加锁的问题。this
在咱们应用程序开发过程当中,常常会遇到一些问题,须要使用多线程技术来加以解决。spa
许多种类的应用程序都须要长时间操做,好比:执行一个打印任务,请求一个 Web Service 调用等。用户在这种状况下通常会去转移作其余事情来等待任务的完成,同时还但愿随时能够监控任务的执行进度。 线程
为何在咱们切换应用程序后,会发生屏幕假死的现象呢?代理
这是由于当你切换当前应用程序到后台再切换回前台时,系统须要在屏幕上重画整个用户界面。可是应用程序正在执行长任务,根本没有时间处理用户界面的重画,问题就会发生。如何解决问题呢?咱们须要将长任务放在后台运行,把用户界面线程解放出来,所以咱们须要另一个线程。
如何避免多线程的窗体资源访问的安全问题呢?其实很是简单,有两种方法:
状况一:
delegate void ShowProgressDelegate(int totalStep, int currentStep);
delegate void RunTaskDelegate(int seconds);
void ShowProgress(int totalStep, int currentStep)
{
progressBar1.Maximum = totalStep;
progressBar1.Value = currentStep;
}
void RunTask(int seconds)
{
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
// 每 1/4 秒显示一次进度
for (int i = 0; i < seconds * 4; i++)
{
Thread.Sleep(250);
this.Invoke(showProgress, new object[] { seconds * 4, i + 1 });
}
}
private void button1_Click(object sender, EventArgs e)
{
RunTaskDelegate runTask = new RunTaskDelegate(RunTask);
// 委托异步调用方式
runTask.BeginInvoke(Convert.ToInt32(this.textBox1.Text), null, null);
}
状况二:
delegate void ShowProgressDelegate(int totalStep, int currentStep);
delegate void RunTaskDelegate(int seconds);
void ShowProgress(int totalStep, int currentStep)
{
if (progressBar1.InvokeRequired)
{
ShowProgressDelegate showProgress = new ShowProgressDelegate(ShowProgress);
this.BeginInvoke(showProgress, new object[] { totalStep, currentStep });
}
else
{
progressBar1.Maximum = totalStep;
progressBar1.Value = currentStep;
}
}
void RunTask(int seconds)
{
// 每 1/4 秒显示一次进度
for (int i = 0; i < seconds * 4; i++)
{
Thread.Sleep(250);
ShowProgress(seconds * 4, i + 1);
}
}
private void button1_Click(object sender, EventArgs e)
{
RunTaskDelegate runTask = new RunTaskDelegate(RunTask);
// 委托异步调用方式
runTask.BeginInvoke(Convert.ToInt32(this.textBox1.Text), null, null);
}