Thread.Join()导致死锁

本文关键字:死锁 Join Thread | 更新日期: 2023-09-27 18:21:46

我总共有三个线程。第一个是主UI线程,它启动System.Threading.ThreadExperimentThread),然后启动BackgroundWorkerWorkerThread)。

CCD_ 5和CCD_。我将对此资源的访问与以下对象同步:

private static readonly Object LockObject = new Object();

我在每个线程的主循环中使用如下:

lock (LockObject)
{
    // Do something with shared resource here.
}

ExperimentThread的精简版本如下:

public void RunExperiment
{
    while (!bStopThread)
    {
        lock (LockObject)
        {
            // Do something with shared resource here.
        }
        if (bStopThread)
        {
            break;
        }
        else
        {
            Application.DoEvents();
            Thread.Sleep(250);
        }
    }
}

为了完整起见,这里是WorkerThread:的DoWork方法

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker Worker = sender as BackgroundWorker;
    for (int X = 0; X < 200; X++)
    {
        if (Worker.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
        lock (LockObject)
        {
            // Do something with shared resource here.
        }
    }
}

当两个线程都自由运行时,这似乎可以很好地工作。

在某个时刻,UI线程将通过将其布尔字段之一设置为true来终止ExperimentThread,然后等待它结束,如下所示:

if (ExperimentThread.IsAlive)
{
    ExperimentThread.StopThread = true;
    ExperimentThread.Join();    // this line seems to cause the deadlock?
}

一旦调用Join(),ExperimentThreadWorkerThread访问的共享资源就会发生死锁,我的应用程序将无限期挂起。这种情况可能发生在10次中的9次。

如果我从上面的代码片段中删除ExperimentThread.Join(),那么死锁永远不会发生,并且ExperimentThread似乎正常终止(然后通过调用CancelAsync()来终止WorkerThread)。

你知道这里可能出了什么问题吗?

(附言:我一直在使用Console.WriteLine()来确定何时获取和释放锁,这让我相信存在死锁。有没有更好的方法来确定这一点,我可能错了?)

Thread.Join()导致死锁

有没有更好的方法来确定这一点,我可能错了?

检查这一点的更好方法是使用类似于VisualStudio的更高级别SKU中提供的并发可视化工具。它将允许您准确地查看锁定了每个线程的内容,以及线程正在等待的处理程序等。

至于死锁的确切原因,没有足够的代码来确定,但常见的问题是:

  1. ExperimentThread和主线程(使用Join()调用)都锁定在同一个对象上,即:在lock(LockObject)语句中
  2. ExperimentThread正在使用Control.Invoke将调用封送回UI线程。由于UI线程被阻塞(在Join()上等待),它永远无法处理消息,这将阻止ExperimentThread完成

话虽如此,一般来说,如果您使用的是.NET 4或更高版本,我建议您使用TaskTask<T>,而不是新的ThreadTask为处理线程提供了一个更好的API,包括允许延续而不是阻塞。C#5对此进行了扩展,甚至允许您异步等待任务完成。