这会导致死锁吗? BeginInvoke()&的Thread.join()

时间:2013-09-03 08:17:04

标签: c# multithreading .net-4.5 begininvoke

我有许多线程可以调用的代码来更新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 = "";
}

超过100次时,在“停止”按钮单击时运行程序死锁。当我看到死锁时我没有调试,所以我无法确定各种线程的状态,但我几乎可以肯定我加入的所有线程最终都会到达他们检查的位置 isStopping值并终止。这让我认为BeginInvoke可能存在问题,但无法真正找到它。它应该是异步的,因此调用它的线程(crawler& indexer)不应该阻塞。如果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);
    });
}

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

1 个答案:

答案 0 :(得分:3)

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

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

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