【问题标题】:Can this cause deadlock? BeginInvoke() & thread.Join()这会导致死锁吗? BeginInvoke() & thread.Join()
【发布时间】:2013-09-03 18:18:04
【问题描述】:

我有许多线程可以调用来更新 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);
    });
}

它似乎在工作,我希望僵局已经过去了......

【问题讨论】:

  • 你试过lblInfo.Invoke(del)吗?
  • @AlessandroD'Andria 遇到这样一个罕见的问题,我不能只是因为它工作过一次而感到高兴,而且在我看来,使用调用确实会导致死锁
  • 就像 Jon Skeet 所说,不要在 GUI 事件处理程序中调用阻塞操作。

标签: c# multithreading .net-4.5 begininvoke


【解决方案1】:

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

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

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

【讨论】:

  • 不需要新线程等待所有线程停止。他们可以在终止或循环等待某事之前将 BeginInvoke 一个“完成”消息作为他们的最后一个操作。 GUI 可以在收到“完成”消息时将它们计数出来并重新计数。
  • 我认为加入主线程不会有问题,因为设置 bool 后工作人员所做的工作很少,我看不到任何冻结,但你是正确的。关于 bool 没有正确读取的事情对我来说是全新的......只有主线程写入它并且 bool 是原子的(不是吗?)所以我认为“没问题”。我想我得看看这个记忆障碍的概念......谢谢,一旦我完全理解它,我就会回来接受这个答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-27
  • 2021-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多