1

我一共有三个线程。第一个是主 UI 线程,它启动一个System.Threading.Thread( ExperimentThread),然后启动一个BackgroundWorker( WorkerThread)。

MainThread并且WorkerThread两者都访问共享资源。我将对该资源的访问与以下对象同步:

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);
        }
    }
}

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

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 线程将ExperimentThread通过将其布尔字段之一设置为 true 来终止,然后等待它结束,如下所示:

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

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

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

有什么想法可能是这里的问题吗?

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

4

1 回答 1

1

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

检查这一点的更好方法是使用Visual Studio 的更高级别 SKU 中可用的Concurrency Visualizer之类的东西。它将允许您准确查看锁定每个线程的内容,以及线程正在等待的句柄等。

至于您遇到死锁的确切原因 - 没有足够的代码来确定这一点,但常见问题是:

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

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

于 2013-07-10T16:16:46.133 回答