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 try1
{
public partial class Form1 : Form
{
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a=0;
while (start_a==true)
{
label1.Invoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (start_b == true)
{
label2.Invoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
Thread.Sleep(5000);
b=b+5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
}
}
}
3 回答
我们需要有关错误发生的地点和时间的更多详细信息。通过查看代码,我的第一个猜测是您在尝试关闭表单时遇到了异常。退出事件处理程序将start_a
and设置start_b
为 false,但不会等到后台线程完成后才让表单运行任何清理代码。您现在在后台线程和表单清理之间有一个竞争条件。此清理代码释放窗口句柄,因此当后台线程在五秒后唤醒并可能尝试将文本更改调用回 UI 线程时,您将失败。
解决该问题的最简单方法是加入()任何活动的后台线程并等待它们完成,然后再让表单完成关闭。一种更正确、更复杂的方法是设置适当的线程同步原语(Mutex、WaitHandle、Sempahore、...),以允许您向线程发出立即停止的信号。
没有简单的解决方案,因为您必须与结束的其他线程同步,但 Invoke 要求在 UI 线程中执行,该线程应该关闭其他线程!所以 tUI 要求 t1、t2 退出,但 t1、t2 可能需要 tUI 才能退出!:)
添加Application.DoEvents();
(读取=处理所有调用请求)到quitting
这样的方法:
private void quitting(object sender, FormClosingEventArgs e)
{
start_a = false;
start_b = false;
Application.DoEvents(); // NOT the solution, is not enough!!!
}
对大多数竞争条件进行排序,但还不够。
为什么?由于这种可能但非常不可能的竞争条件:
t1 before queuing Invoke
~~~~~~~>
start_a = false; start_b= false; Application.DoEvents();
<~~~~~~~
t1 queue an Invoke
~~~~~~~> (very improbable but possible)
(continue trough disposing)
<~~~~~~~
queued Invoke on disposed label -> crash!
锁定检查启动变量状态的关键部分并清空消息队列应该可以解决问题。你的练习:找到其他可能的竞争条件,并找到一种在最坏情况下在 5 秒内退出的方法(提示:不要使用睡眠。睡眠是魔鬼)。
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 WindowsFormsApplication1
{
public partial class Form1 : Form
{
object _closing1;
object _closing2;
volatile bool start_a = false;
volatile bool start_b = false;
public Form1()
{
InitializeComponent();
button1.Text = "Click to start";
button2.Text = "Click to start";
_closing1 = new object();
_closing2 = new object();
}
private void button1_Click(object sender, EventArgs e)
{
if (start_a == false)
{
button1.Text = "Running";
start_a = true;
Thread thread2 = new Thread(new ThreadStart(th1));
thread2.Start();
}
else
{
button1.Text = "Click to start";
start_a = false;
}
}
void th1()
{
int a = 0;
while (true)
{
lock (_closing1)
{
if (start_a == false)
break;
label1.BeginInvoke((MethodInvoker)(() => label1.Text = Convert.ToString(a)));
}
Thread.Sleep(50);
a++;
}
}
void th2()
{
int b = 0;
while (true)
{
lock (_closing2)
{
if (start_b == false)
break;
label2.BeginInvoke((MethodInvoker)(() => label2.Text = Convert.ToString(b)));
}
Thread.Sleep(5000);
b = b + 5;
}
}
private void button2_Click(object sender, EventArgs e)
{
if (start_b == false)
{
button2.Text = "Running";
start_b = true;
Thread thread2 = new Thread(new ThreadStart(th2));
thread2.Start();
}
else
{
button2.Text = "Click to start";
start_b = false;
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
lock (_closing1)
{
start_a = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_a = false
// 2) t1 is out of the critical section
Application.DoEvents();
}
lock (_closing2)
{
start_b = false;
// Clear the message queue now so access on disposed lables is possible.
// No more invokes will be queued because 1) start_b = false
// 2) t2 is out of the critical section
Application.DoEvents();
}
}
}
}
我认为问题在于您正在更新一个线程上的 UI 控件,而不是创建它的线程;我认为你应该看看这个: 如何从 C# 中的另一个线程更新 GUI?或在这里:http: //msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx一些例子比需要的更复杂,但它的要点是你有从创建控件的同一线程更新控件;Control.InvokeRequired 是您要注意的。