如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件

Posted

技术标签:

【中文标题】如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件【英文标题】:How to update GUI controls with versions of Delphi (Pre Delphi 2010) without Synchronize 【发布时间】:2013-11-21 16:00:55 【问题描述】:

我有一些使用 Delphi 2010 和 XE4 构建的应用程序,它们在线程中使用同步。我认为 Synchronize 是在 Delphi 2010 中引入 Delphi 的。我的线程运行良好,所以这不是问题。

我的问题是:有什么方法可以与 Delphi 2010 之前的 Delphi 版本“同步”,或者以不同的方式询问,如何在没有同步的情况下更新这些早期版本的 Delphi 中的 GUI 控件?

下面显示的代码是实际代码的子集,以减少本文的长度。

type
   A TThread descendent for loading Images from a folder 
  TFolderLoadingThread = class(TThread)
  private
     Private declarations 
    AspectRatio: double;
  protected
     Protected declarations 
    procedure Execute; override;
  public
     Public declarations 
    constructor Create(CreateSuspended: Boolean);
  end;

procedure TFolderLoadingThread.Execute;
 Load images ImageEnView in the thread. 
begin
  inherited;
   Free the thread onTerminate 
  FreeOnTerminate := True;
  if not Terminated then
  begin
     Set the Progressbar.Max Value 
    **Synchronize**(
      procedure
      begin
         if iFileCount > 0 then
          Form1.Gauge1.MaxValue := iFileCount - 1;
 end);
end;

【问题讨论】:

Synchronize 适用于所有版本的 Delphi(因为 TThread 已经存在)。 TThread.Synchronize 一直都在。您是在问匿名方法吗? 一个很好的同步总结:***.com/a/1806947/327083 【参考方案1】:

Synchronize 是一个非常古老的例程,但在 D2009 之前的 Delphi 版本中没有匿名过程。在这些版本中,Synchronize 旨在调用不带参数的方法。

procedure TFolderLoadingThread.UpdateProgress;
begin
 if iFileCount > 0 then
          Form1.Gauge1.MaxValue := iFileCount - 1;
end;

在执行中:

do thead work...
Synchronize(UpdateProgress);

附:您不必在 Execute 正文中调用 Terminate

【讨论】:

换句话说:将同步代码移动到该线程类的单独方法中,并通过 Synchronize 调用此方法。 @Uwe- 你能显示一些代码还是我应该显示线程类...它很短。 procedure TFolderLoadingThread.UpdateGUI; begin Form1.Gauge1.MaxValue := iFileCount - 1; end; 并由Synchronize(UpdateGUI);调用 @Bill 与其要求我们提供代码,不如自己尝试一下吗? @David - 是的,我想我明白了,所以我可以自己尝试一下。【参考方案2】:

    同步几乎永远存在。我在 Delphi 5 中使用过。但上面已经说过了。

    1234563 .例如,如果有一个很长的计算,并且您想要一个计量器“完成 5678 个中的 1234 个” - 那么停止计算过程就没有什么意义了。让计数器不准确,加减一打。为什么要关心?

现在,如何实现它...对于简单的情况,您可以使用直接 Windows 访问。 例如,如果我们有一个 windowed 文本标签 - 继承自 TWinControl

http://docwiki.embarcadero.com/Libraries/XE4/en/Vcl.StdCtrls.TStaticText

然后我们可以绕过 VCL 及其所有优点并使用标准 Windows API 更改标题

http://msdn.microsoft.com/en-us/library/windows/desktop/ms632644.aspx http://msdn.microsoft.com/en-us/library/windows/desktop/ms644944.aspx

您还必须正确计算要显示的总数和消息。然而,这与 GUI 和 VCL 无关。但希望您了解同步原语,例如

http://www.freepascal.org/docs-html/fcl/syncobjs/tcriticalsection.html

作为。是的,有 AtomicIncrement 给你,我只是详细展示了框架。

下面我假设 TMyThread 是工作人员池之一,处理一些常见的工作项。它可能是唯一的工作线程或许多工作线程之一,它只是不知道,也不关心。

TThreadsCoordinator = class
   ...
 private
   CounterCS; TCriticalSection;

 public
   property CompleteItemsCount: integer 
            read FCompleteItemsCount;
   function IncCompleteItemsCount(const Delta: integer): integer;
end; 

...

function TThreadsCoordinator.IncCompleteItemsCount;
begin
  CounterCS.Acquire;
  try
    Inc(FCompleteItemsCount, Delta);
  finally
    CounterCS.Release;
  end;
  Result := FCompleteItemsCount;
end;

var GlobalCaptionBuffer: array[0..127] of char; 
// should be filled through with #0 before threads are started

procedure TMyThread.WorkItemComplete;
var s: string;
begin
 ...
 s := Format('Done: %d of %d', 
       [ Self.Coordinator.IncCompleteItemsCount( +1 ),
         Self.Coordinator.TotalItemsCount ] );

 StrCopy( @GlobalCaptionBuffer[0], PChar(s));

 if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
    PostMessage( MyProgressForm1.StaticText1.Handle, 
       WM_SETTEXT, 0, Integer(@GlobalCaptionBuffer[0]));
...
end;

这个实现——虽然是直截了当的——可能会遇到同步问题。 * 你需要一些文本的全局缓冲区,在 WM_SETTEXT 实际接收和执行之前不会被删除。 * 您需要考虑会发生什么,当该缓冲区中的文本正在更新时,标签会同时读取它 * 下面的 David Heffernan 声称,在某些情况下,Windows 控件可能正在销毁和重新创建过程中(阅读:RecreateWnd 方法),因此可能会发生意想不到的后果。就个人而言 - 只要 ProgressForm 只能由 WinGDI 控制,就意味着避免所有 VCL 好东西(例如在旅途中更改组合框样式,这确实会触发 RecreateWnd,但本机 WinGDI API 不可能) - 我不明白为什么可以发生。但大卫声称它可以。自己选择,如果适合你。

但是对于更复杂的任务(或使用无窗口文本标签),您可以求助于“消息方法”——VCL 的基础。

http://www.freepascal.org/docs-html/ref/refsu31.html http://docwiki.embarcadero.com/RADStudio/XE4/en/Methods

这种实现更灵活(将数据生成和数据可视化分开)并且可以说更可靠(请参阅 cmets 中的 RecreateWnd 注释)。但它需要更多样板代码。

const WM_IncCounter = WM_USER + 10;

type TMyProgressForm = class(TForm)
private
   FCompleteItemsCount: integer;
   procedure IncCounter(var Msg: TMessage ); message WM_IncCounter;
...
end;

procedure TMyProgressForm.IncCounter(var Msg: TMessage );
var s: string;
begin
  Inc(FCompleteItemsCount, Msg.WParam);
  s := Format('Done: %d of %d', 
       [ FCompleteItemsCount, TotalItemsCount ] );
  Label1.Caption := s;
  ProgressBar1.Position := FCompleteItemsCount;
end;


procedure TMyThread.WorkItemComplete;
begin
  Inc(Self.UncommittedTicks);

  if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
  begin
    PostMessage( MyProgressForm1.Handle, WM_IncCounter, Self.UncommittedTicks, 0);
    Self.UncommittedTicks := 0;
  end;
end;

【讨论】:

带有WM_SETTEXT 的PostMessage 是错误的。重新创建窗口打破了这一点。您最终可以在工作线程上创建窗口句柄。请记住,Handle 可以导致调用 CreateWnd @DavidHeffernan 我假设在线程运行时已经显示进度窗口。因此,所有组件都已经有了句柄——它们被渲染在屏幕上。好吧,我可以重写它以使用 docwiki.embarcadero.com/Libraries/XE3/en/… 代替(使用 >0 保护) @Arioch'The 是否已经显示没关系。可能会重新创建窗口。 WindowHandle 也不好。没有同步,所以你有一个数据竞争。当然,您会发现很难显示错误,但这并不意味着代码是正确的。您应该将您的消息发布到分配的窗口,例如,AllocateHWnd @DavidHeffernan 为什么可以重新创建位于 TForm 上的 TStaticText ?这不像我会更改组合框样式。如果比赛发生,那么一两条消息将被发布到无效句柄并被丢弃。大不了。 @Arioch'The 因为它只被创建一次。看看在ShowModal 中对RecreateWnd 的调用。通常很难证明永远不会重新创建窗口句柄。这就是为什么我不费心尝试这样做的原因。另一方面,AllocateHWnd 很好理解。

以上是关于如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件的主要内容,如果未能解决你的问题,请参考以下文章

我如何在不使用Delphi约束的情况下确定泛型类型为nil?

如何在不写入磁盘的情况下将 XML 从 Delphi 传递到 C#?

如何在不冻结 gui 的情况下运行 QProcess 的同步链?

OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?

如何在不解析的情况下在javascript中同步包含JSON数据?

如何在不同步的情况下使用多个线程(2、4、8、16 个线程)循环打印字符串(10,100、1000 个循环)?