【问题标题】:Critical section not working关键部分不工作
【发布时间】:2014-02-13 06:10:24
【问题描述】:

我有这个主窗体

TForm1 = class(TForm)    
  fReceiver: TMessageReceiver;
  fCS: TCriticalSection;  
  constructor Create(aOwner: TComponent); override; **Create Critcal section**
  destructor Destroy(); override; **Delete critical section**
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  fcs.Acquire;
  //Perform Action
  fcs.Release;

end;

TMessageReceiver= class(TReceiver)
  private
    fFrame: TForm1;
  public
    constructor Create(aFrame: TForm1);
    destructor Destroy(); override;
    function HandleMessage(aUserName: string): boolean; override;
  end;

 function TMessageReceiver.HandleMessage(aUserName: string): boolean;
 begin
   fFrame.fcs.Acquire;
    //Do Lengthy OP
   fFrame.fcs.Release;
 end;

我有这两个类,其中 TMessageReceiver 在一个线程中被调用。我尝试在这两个类之间进行同步。调用句柄消息后,如果我尝试单击按钮,它将进入关键部分块,即使它已经在 HandleMessage 中获取。 我不知道这段代码有什么问题。

任何建议都会有很大帮助。

【问题讨论】:

  • 你显示的代码没有问题,所以一定是你没有显示的代码有问题。请提供SSCCE
  • 代码似乎没问题。你不要让我们从 TMessageReceiver 看到 Create。所以会出现一个问题:它是同一个关键部分,还是周围有多个 form1 实例,因此有多个关键部分?
  • @RemyLebeau:这是一个非常大的代码,所以我不知道如何显示它。为了简化事情,我删除了关键部分的所有用法,只在按钮单击中使用它并删除了版本。现在,当我单击按钮两次时,即使我没有释放它,它也会进入块。为什么会这样?
  • @RitsaertHornstra:我在表单 1 实例的构造函数和析构函数中放置了一个调试点,它被调用一次。有什么方法可以确定是否有多个临界区实例?
  • @Jeeva:我不是要你的全部代码,只是一个小的SSCCE 重现了同样的问题。至于其余的,当一个线程已经获得了 CS 锁的所有权时,它可以根据需要多次重新进入同一个 CS。唯一的要求是每个 Acquire 必须有一个 Release 以保持 CS 的内部锁计数器平衡。

标签: multithreading delphi


【解决方案1】:

Acquire 的调用成功有两种可能的解释。

  1. 临界区是无主的。
  2. 临界区归调用线程所有。

您可以使用临界区结构的DebugInfo 字段来检查递归计数:http://msdn.microsoft.com/en-gb/magazine/cc164040.aspx

选项 1(未拥有关键部分)的一个可能原因是您创建了多个关键部分实例。

选项 2 的可能原因是您的所有代码都在同一个线程中运行。

我们无法为您提供更详细的诊断,因为您从问题中删除了所有基本细节。这是一个典型的情况,SSCCE 可以帮助每个人。


从更广泛的角度审视您的代码,它显示出令人担忧的迹象。在TMessageReceiver.HandleMessage 中,您获得了一个临界区并开始执行一个冗长的操作。但随后在TForm1.Button1Click(输入事件处理程序)中,您尝试声明相同的临界区。如果您在冗长的操作正在进行时这样做,您将阻塞 UI 线程。您将拥有一个无响应的 UI,并且 Windows 可能会将您的窗口标记为无响应并显示它。

在输入事件处理程序中看到关键部分是非常令人惊讶的。这具有成为主要设计缺陷的所有症状。

【讨论】:

  • 我认为你是对的。深入挖掘代码,似乎消息接收器不是线程,而是事件处理程序。所以我认为它在同一个线程中。现在我应该使用其他机制来同步类似事件吗?
  • 如果它在同一个线程中,它应该不能同时执行两次。除非您在某处调用了 Application.ProcessMessages。您可以通过调用 getGetCurrentThreadId 来检查它是哪个线程
  • 如果你所有的代码都运行在同一个线程上,那你为什么需要同步呢?
  • @David,我怀疑是因为他试图隐藏真正的问题。他可能在某处调用 ProcessMessages。这可以解释这个问题,特别是因为他说这是一个事件,因此它可能通过 PostMessage 通知,然后在错误的地方进行 ProcessMessages 可能会导致这种情况。
【解决方案2】:

临界区确保没有其他线程同时进入受保护的代码段,它不能防止从同一线程重新进入。这意味着如果您在受保护的代码部分中的任何地方碰巧调用Application.ProcessMessages,您很容易遇到重入问题。只是猜测......

【讨论】:

  • 这是一个很好的猜测。我看不出有任何其他方式可以实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 1970-01-01
  • 2014-02-07
  • 2012-06-17
  • 2011-09-26
相关资源
最近更新 更多