Nutshell:我从我的表单中启动一个线程,然后一段时间后使用它的Join方法。它终止但我的应用程序停留在Join上,并拒绝承认它已完成加入。什么会导致这种情况发生?我的线程是从我的表单上的一个按钮启动的,并尝试从同一表单上的第二个按钮加入。
更多信息: 我有一个使用线程来完成通信和数字运算的应用程序。假设主窗体是父线程,第一个子节点是Child1。启动时,Child1与外部设备建立了一些通信,并启动了自己的2个子线程(Child2和Child3)来处理传入的数据。
当用户决定应用程序要停止处理传入数据时,我需要Child1终止(因此,如果需要,可以在恢复之前更改com设置)。我设置了一个停止事件,Child1退出了它的执行循环,它做的第一件事就是通知Child2和Child3不再需要它们(通过另一个停止事件),Child2中的Join方法等待Child2和Child3。这很好用。
不起作用的是,在设置了促使Child1退出运行循环并终止的停止事件后,表单也在Child1上使用了Join方法,但是,这个Join无限期地等待。
单步执行:当我单步执行我的应用程序时,我注意到在使用Join之前,IsAlive属性为true。在我点击Child1.Join()后,我再也无法从线程中获取任何信息,因为它位于“JoinWaitSleep”中。但是,如果我运行while循环导致表单线程在Child1.IsAlive为true时休眠,那么这很好。我的第二个按钮是否是某个无法将Child1连接到它的线程的一部分?
public void Run()
{ //Known as Child1
//code to setup coms is here
//Launch Builder threads
InsertBackgroundMonitor("Launching Collector Threads");
RunBuilders = true;
//Known as Child2 and Child3
BuildThread1 = new Thread(new ThreadStart(Cam1Builder));
BuildThread2 = new Thread(new ThreadStart(Cam2Builder));
BuildThread1.Start();
BuildThread2.Start();
while (!StopEventHandle.WaitOne(0, true))
{
//// Code that waits for coms and tosses data into lists
}
RunBuilders = false;
//Wait for threads to terminate
BuildThread1.Join();
BuildThread2.Join();
}
private void RunButton_Click(object sender, System.EventArgs e)
{
//Button for running the control thread
ControlThread = new Thread(new ThreadStart(Run));
ControlThread.Start();
}
private void StopButton_Click(object sender, System.EventArgs e)
{
if (btnStop.Enabled)
{ //Button for stopping the control thread
StopEventHandle.Set();
if (ControlThread != null)
{
while (ControlThread.IsAlive)
{
Thread.Sleep(100);
}
//somehow Join did not work
//ControlThread.Join();
}
//Update buttons
btnStart.Enabled = true;
btnStop.Enabled = false;
}
}
答案 0 :(得分:2)
如果没有更多代码,很难说,但是您已经启动了一些资源,但是您没有正确关闭。
Join()等待线程完全关闭,并在返回之前释放所有资源。如果线程有任何BackgroundWorker任务,或者它有任何你没有显示仍在运行的备用任务,它将不会返回。
由于BuildThread1和BuildThread2都正确返回,并且加入了,您可以确定它不是其中之一,也不是他们正在做的任何事情。查看其余代码。它做了什么?
编辑: 这很好用:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Thread ControlThread;
Thread BuildThread1;
Thread BuildThread2;
volatile bool RunBuilders = true;
volatile bool RunControl = true;
private void button1_Click(object sender, EventArgs e)
{
ControlThread = new Thread(new ThreadStart(Run));
ControlThread.Start();
}
private void button2_Click(object sender, EventArgs e)
{
RunControl = false;
if (ControlThread != null)
{
while (ControlThread.IsAlive)
{
Thread.Sleep(100);
}
//somehow Join did not work
ControlThread.Join();
}
}
public void Run()
{ //Known as Child1
//code to setup coms is here
//Launch Builder threads
RunBuilders = true;
//Known as Child2 and Child3
BuildThread1 = new Thread(new ThreadStart(Cam1Builder));
BuildThread2 = new Thread(new ThreadStart(Cam1Builder));
BuildThread1.Start();
BuildThread2.Start();
while (RunControl)
{
//// Code that waits for coms and tosses data into lists
}
RunBuilders = false;
//Wait for threads to terminate
BuildThread1.Join();
BuildThread2.Join();
}
public void Cam1Builder()
{
while ( RunBuilders )
{
}
}
}
}
答案 1 :(得分:1)
我只能猜测导致这种情况的可能问题,因为我没有看到Cam1Builder
和Cam2Builder
方法的代码。 1 立即跳转的可能性对我说:
Join
阻止执行调用线程。如果这是从UI线程完成的,那么它会停止消息泵。如果工作者试图将消息发布到UI线程(例如,通过Control.Invoke
),则两个线程都将死锁。RunBuilders
未标记为volatile
或未在lock
内访问,那么其他线程所感知的值无法在任何给定时间点进行预测。通常,不建议从UI线程调用Join
。 UI线程的特殊之处在于它们运行的是一个无限循环,它调度和处理Windows消息。通过调用Application.Run
启动此循环,您应该在代码中的某处看到它。如果通过调用Join
或其他阻塞方法来阻止UI线程,则它无法处理排队的消息。您可能正在使用Control.Invoke
来编组来自其中一个工作线程的UI线程上的委托的执行。如果是这种情况,那么两个线程都会死锁。 Join
阻止等待工作线程的UI线程。 Control.Invoke
阻止等待UI线程的工作线程。
用作线程之间通信机制的变量应该是线程安全的。在您的情况下,您使用RunBuilders
作为在工作线程上发出操作信号的机制。问题是,由于RunBuilders
既未标记为volatile
,也未从lock
块中访问,因此其值无法可靠地传输到工作线程。您可能在一个线程中将其值设置为true
,但另一个线程可能会无限期地继续读取false
。
1 如果您发布更多代码,我可能会提供更好的见解。具体来说,我希望看到Cam1Builder
或Cam2Builder
正在做什么的大致轮廓,特别是它正在阅读的部分RunBuilders
。
答案 2 :(得分:1)
您是否尝试将RunBuilders更改为volatile?我个人不知道,因为我以这种方式使用bool交叉线程,但如果你要去它应该是volatile,所以所有线程都看到更新而不是由JIT“缓存”的陈旧值。
除此之外,还需要看看其他两个线程在做什么。