1

我有许多线程可以调用来更新 GUI 的代码:

MethodInvoker del = () => { lblInfo.Text = tmp; };
lblInfo.BeginInvoke(del);

(lblInfo 由 GUI 线程创建)

我也有这个方法在 GUI 线程执行的按钮单击时调用:

public void Stop()
{
    isStopping = true;
    crawler.Join();
    foreach (Thread t in txtWorkers)
    {
        t.Join();
    }
    indexer.Join();     
    lblStatus.Text = "Stopped";
    lblInfo.Text = "";
}

1 次超过 100 次在停止按钮单击时运行程序死锁。当我看到死锁时我没有调试,所以我不能确定各个线程的状态,但我几乎可以肯定我加入的所有线程最终都会到达它们检查 isStopping值并终止的点。这使我认为可能存在问题,BeginInvoke但无法真正找到它。它应该是异步的,因此调用它的线程(爬虫和索引器)不应该阻塞。如果 GUI 线程正在执行Stop()并且还必须执行来自 的调用,会发生BeginInvoke什么?这可能是问题吗?我加入的线程有什么我看不到的吗?

编辑: 建议更改后的代码是什么样的:

public void Stop()
{
    /*
     ...disable GUI
     */

    isStopping = true; // Declared as volatile
    lblStatus.Text = "Stopping...";

    // Creating a thread that will wait for other threads to terminate
    Task.Factory.StartNew(() =>
    {    
        crawler.Join();
        foreach (Thread t in txtWorkers)
        {
            t.Join();
        }
        indexer.Join();

        // Adjust UI now that all threads are terminated
        MethodInvoker del = () =>
        {
            /*
            ...enable GUI
            */
            lblStatus.Text = "Not Running";
            isStopping = false;
        };
        lblStatus.BeginInvoke(del);
    });
}

它似乎正在工作,我希望僵局消失......

4

1 回答 1

4

我不认为这应该是一个问题,因为您使用的是BeginInvoke而不是Invoke- 后台线程将直接越过该行,而无需等待 GUI 赶上。如果你在Control.Invoke任何地方使用,那可能会导致死锁。

更重要的是,Join在你的 GUI 线程中使用从根本上来说是个坏主意——UI 将被冻结,直到一切都完成。最好禁用任何可以启动新事物的控件,设置您的isStopping标志,然后创建一个新线程以等待所有线程停止 - 当所有线程都完成时,然后再次更新 UI BeginInvoke。(如果您使用的是 .NET 4.5,您还可以为此使用异步方法,创建并等待任务以等待所有线程。)

最后,如果isStopping只是一个bool字段,则无法保证您的后台线程会“看到”来自 UI 线程的更改。使字段 volatile 可能会解决此问题,但 volatile 的确切含义让我害怕。另一种方法是使用Interlocked该类,或使其成为一个属性,该属性获得读取和写入的锁定 - 确保适当的内存屏障到位。

于 2013-09-03T08:20:48.403 回答