【问题标题】:C#.NET Threading QuestionC#.NET 线程问题
【发布时间】:2011-07-28 05:36:57
【问题描述】:

我遇到了 C#.NET 应用程序中线程之间的通信问题。 希望有人能就可能的解决方案指导我正确的方向。

我有一个 C#.NET 应用程序。它是一个 Windows 窗体应用程序。 我的应用程序有两个线程 - 一个线程是主线程(UI 线程),另一个是子线程。让我们称子线程为“workerThread” 应用程序中只使用了一个表单。让我们将此表单称为“MainForm”

子线程在 MainForm 加载时启动(使用表单的“Load”事件处理程序启动线程)

在 MainForm 类中,我有一个名为“stopWork”的变量,它是一个公共布尔变量,它用作指示子线程是应该继续工作还是应该停止的标志

我有另一个类(除了 MainForm 类),它包含我在子线程中执行的方法。让我们将这第二类称为“WorkerClass”。 我将对当前表单(MainForm)的引用传递给“WorkerClass”的构造函数

我在主窗体中有一个“停止”按钮,如果单击它,它将“stopWork”设置为“true”,然后调用“workerThread.Join()”以等待子线程完成执行。

在子线程中,方法“doWork”在for循环中不断检查“parentForm.stopWork”的状态。如果“stopWork”设置为“true”,则循环中断,随后方法结束。

现在的问题是,一旦我单击“停止”按钮,应用程序就会挂起。

我在下面粘贴部分代码以便于理解:

public partial class MainForm : Form
{
    Thread workerThread = null;
    ThreadStart workerThreadStart = null;
    WorkerClass workerClass = null;

    public bool stopWork = true;

    /*.......... some code ............*/

    private void MainForm_Load(object sender, EventArgs e)
    {
        workerThreadStart = new ThreadStart(startWork);
        workerThread = new Thread(workerThreadStart);
        stopWork = false;
        workerThread.Start();
    }

    private void startWork()
    {
        workerClass = new WorkerClass(this);
    }

    private void buttonStop_Click(object sender, EventArgs e)   //"stop" button
    {
        if (workerThread != null)
        {
            if (workerThread.IsAlive == true)
            {
                stopWork = true;
                workerThread.Join();
            }
        }
    }

    /*.......... some more code ............*/

}

public class WorkerClass
{
    MainForm parentForm=null;

    /*......... some variables and code ........*/

    public WorkerClass(MainForm parentForm)
    {
        this.parentForm=parentForm;
    }

    /* .............. some more code ...........*/

    public void doWork()
    {
       /*.......... some variables and code ...........*/

       for(int i=0;i<100000;i++)
       {
           // ** Here is the check to see if parentForm has set stopWork to true **
           if(parentForm.stopWork==true)
              break;

           /*......... do some work in the loop ..........*/


       }

    }

    /********* and more code .........*/
}

我想我可能知道问题出在哪里。 问题在于子线程中的“doWork”方法试图访问父表单中的“stopWork”变量,而父表单已经通过调用“workerThread.Join()”方法被阻止。所以,我认为这是一个“死锁”问题。

我在确定问题方面是否正确?还是我错了,问题出在其他地方?

如果这确实是一个死锁,有什么可能的解决方案来解决这个问题?

我做了一些谷歌搜索,发现了很多关于线程同步以及如何避免死锁的资源。但我不明白如何将它们专门应用于我的问题。

非常感谢任何有关解决此问题的帮助或指导。

【问题讨论】:

  • 我想查看您的工作线程类的一部分,看看它如何检测是否已请求停止工作。你能把它包括进去吗?
  • 您是否正在从您的线程更新 GUI?
  • @Lasse V. Karlsen:是的,我正在从子线程更新 GUI。我在富文本框控件上调用 Invoke 以将消息写入 UI。

标签: c# multithreading deadlock thread-synchronization


【解决方案1】:

是的,您编写的代码非常容易出现死锁。 BackgroundWorker 类特别容易导致这种死锁。

问题出在我们无法在您的 sn-p WorkerClass 中看到的代码中。您肯定会在那里做一些以某种方式影响 UI 的事情,这始终是首先考虑创建线程的主要原因。您可能使用 Control.Invoke() 在 UI 线程上运行一些代码并更新控件。也许也可以表示工作线程已完成,例如,将按钮的 Enable 属性设置回 true。

那是死锁城市,这样的代码不能运行,直到 UI 线程空闲,回到抽它的消息循环。在你的情况下它永远不会空闲,它卡在 Thread.Join() 中。工作线程无法完成,因为 UI 线程不会空闲,UI 线程不能空闲,因为工作线程没有完成。死锁。

BackgroundWorker 也有这个问题,除非 UI 线程空闲,否则 RunWorkerCompleted 事件无法运行。您需要做的是不阻塞 UI 线程。说起来容易做起来难,BGW 可以帮助你做到这一点,因为它在完成时会运行一个事件。您可以让此事件在 Thread.Join() 调用之后执行您现在在代码中执行的任何操作。你需要在你的类中使用一个布尔标志来表明你处于“等待完成”状态。 This answer有相关代码。

【讨论】:

  • 感谢您的洞察力。你是正确的。我确实在富文本框控件上使用 Invoke 向 UI 写入消息! :-)
  • 我认为从 Hans 的回答和类似的回答中拿走的主要观点是线程很难正确处理。您绝对需要全面了解自己在做什么、其他线程在做什么以及它们如何交互。
  • 您似乎在说 BackgroundWorker 会使这个问题变得更糟,而 BackgroundWorker 是这个问题的最终解决方案。
  • 是的,如果死锁的原因不清楚,BGW更糟糕,因为RunWorkerCompleted事件保证死锁。使用 BGW 可以有效地解决它,因为它有一个 RunWorkerCompleted 事件,允许删除 Thread.Join()。没有矛盾。
  • 非常感谢您提供宝贵的见解。我找到了解决这个问题的方法。本质上,即使在 GUI 线程等待子线程上的 Join() 调用之后,我的代码也在富文本框控件上调用 Invoke。因此,我使用一个布尔标志来指示“等待完成”阶段,并且每当标志为“真”时,我不会将消息传递给富文本框,而是在 StringCollection 对象中收集来自子线程的所有消息。之后子线程完成,我打印了存储在 String Collection 对象中的消息。
【解决方案2】:

使用BackgroundWorker 代替此任务。当你想停止任务的执行时,调用后台worker的CancelAsync方法。

一般来说,如果您对多线程没有专家级的理解(即使这样仍然很危险),滚动您自己的线程代码(在任何平台上)都会导致灾难。

【讨论】:

  • BackgroundWorker 肯定是一个值得考虑的选项。但是,如果我想用自己的线程处理任务怎么办?那该怎么办呢?
  • 这就像听到有人说“打电话给消防部门肯定是一个可以考虑的选择。但是,如果我想自己扑灭燃烧的房子怎么办?”我建议完成这些教程:msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx
  • LOL..好吧,你肯定不会叫消防部门来扑灭“厨房火灾” :-) 开个玩笑,我喜欢自己处理线程,除非它非常复杂的多线程场景。这是一个简单的“开始线程 > 检查条件 > 停止线程”案例。它甚至不涉及线程访问的任何共享资源。它所需要的只是一种在不遇到死锁的情况下检查标志的方法。
  • 我认为当您单击停止按钮时您的应用程序已锁定,因为您正在加入 workerThread,这是一个您无限期保持活动的线程,因为您将其维护为类级变量. doWork() 永远不会被调用(除非它在其中一个被剪断的部分中被调用),如果它被调用也没关系。同样,我建议通过教程(实际运行代码)并更好地了解一切应该如何工作。
  • 这是另一个:msdn.microsoft.com/en-us/library/7a2f3ay4(v=vs.80).aspx#Y170 这似乎或多或少正是您正在做的事情。一个特别的提示是为您的stopWork 变量使用volatile 关键字。 volatile 是一种简单而廉价的同步方式(仅适用于布尔值)。
猜你喜欢
  • 2012-09-20
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 2023-03-21
  • 2022-01-08
  • 2011-09-30
  • 1970-01-01
相关资源
最近更新 更多