当前位置: 首页 > 编程笔记 >

Embarcadero Delphi 使用线程进行后台工作的响应式GUI,使用PostMessage从线程进行报告

杨昆
2023-03-14
本文向大家介绍Embarcadero Delphi 使用线程进行后台工作的响应式GUI,使用PostMessage从线程进行报告,包括了Embarcadero Delphi 使用线程进行后台工作的响应式GUI,使用PostMessage从线程进行报告的使用技巧和注意事项,需要的朋友参考一下

示例

要在长时间运行过程中使GUI保持响应状态,需要进行一些非常精心的“回调”以允许GUI处理其消息队列,或者使用(后台)(工作线程)线程。

通常,启动任意数量的线程来执行某些工作不是问题。当您想让GUI显示中间和最终结果或报告进度时,乐趣就开始了。

在GUI中显示任何内容都需要与控件和/或消息队列/泵进行交互。那应该总是在主线程的上下文中完成。永远不要在任何其他线程的上下文中。

有很多方法可以解决这个问题。

这个例子显示了如何使用简单的线程,允许它通过设置完成线程实例后的GUI访问做FreeOnTerminate来false使用,当一个线程“完成”的报告PostMessage。

关于竞争条件的注释:对辅助线程的引用以表格的形式保存在数组中。线程完成后,数组中的相应引用将变为nil-ed。

这是比赛条件的潜在来源。与使用“运行”布尔值一样,可以更轻松地确定是否还有任何线程需要完成。

您将需要确定是否需要使用锁来保护这些资源。

就本例而言,没有必要。它们仅在两个位置进行修改:StartThreads方法和HandleThreadResults方法。这两种方法仅在主线程的上下文中运行。只要您保持这种方式并且不从不同线程的上下文开始调用这些方法,它们就不可能产生竞争条件。

线

type
  TWorker = class(TThread)
  private
    FFactor: Double;
    FResult: Double;
    FReportTo: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create(const aFactor: Double; const aReportTo: THandle);

    property Factor: Double read FFactor;
    property Result: Double read FResult;
  end;

构造函数只设置私有成员并将FreeOnTerminate设置为False。这是必不可少的,因为它将允许主线程向线程实例查询其结果。

execute方法进行计算,然后将一条消息发布到在其构造函数中接收到的句柄,以表示已完成:

procedure TWorker.Execute;
const
  Max = 100000000;var
  i : Integer;
begin
  inherited;

  FResult := FFactor;
  for i := 1 to Max do
    FResult := Sqrt(FResult);

  PostMessage(FReportTo, UM_WORKERDONE, Self.Handle, 0);
end;

PostMessage在此示例中,必不可少的使用。PostMessage“ just”将消息放入主线程的消息泵的队列中,而不等待它被处理。它本质上是异步的。如果要使用,SendMessage您将自己编码为泡菜。SendMessage将消息放入队列,并等待直到消息被处理。简而言之,它是同步的。

自定义UM_WORKERDONE消息的声明声明为:

const
  UM_WORKERDONE = WM_APP + 1;
type
  TUMWorkerDone = packed record
    Msg: Cardinal;
    ThreadHandle: Integer;
    unused: Integer;
    Result: LRESULT;
  end;

的UM_WORKERDONE常量的用途WM_APP,作为其值的起点,以确保它不与由Windows或Delphi的VCL(所推荐的微软)使用的任何值干扰。

形成

任何形式都可以用来启动线程。您需要做的就是向其中添加以下成员:

private
  FRunning: Boolean;
  FThreads: array of record
    Instance: TThread;
    Handle: THandle;
  end;
  procedure StartThreads(const aNumber: Integer);
  procedure HandleThreadResult(var Message: TUMWorkerDone); message UM_WORKERDONE;

哦,示例代码假定Memo1: TMemo;表单的声明中存在a ,它用于“记录和报告”。

该FRunning可用于防止GUI从开始时的工作是怎么回事被点击。FThreads用于保存实例指针和创建的线程的句柄。

启动线程的过程非常简单。首先检查是否已经有一组线程正在等待。如果是这样,它将退出。如果不是,则将标志设置为true,并启动为线程提供各自的句柄的线程,以便它们知道将其“完成”消息发布到何处。

procedure TForm1.StartThreads(const aNumber: Integer);
var
  i: Integer;
begin
  if FRunning then
    Exit;
    
  FRunning := True;

  Memo1.Lines.Add(Format('Starting %d worker threads', [aNumber]));
  SetLength(FThreads, aNumber);
  for i := 0 to aNumber - 1 do
  begin
    FThreads[i].Instance := TWorker.Create(pi * (i+1), Self.Handle);
    FThreads[i].Handle := FThreads[i].Instance.Handle;
  end;
end;

线程的句柄也放置在数组中,因为这是我们在告诉我们线程已完成的消息中收到的信息,并将其置于线程实例的外部使得访问起来稍微容易一些。如果不需要实例来获取结果(例如,如果它们已存储在数据库中)FreeOnTerminate,True则在线程实例外部使用句柄还允许我们使用set 。在这种情况下,当然无需保留对该实例的引用。

有趣的是在HandleThreadResult实现中:

procedure TForm1.HandleThreadResult(var Message: TUMWorkerDone);
var
  i: Integer;
  ThreadIdx: Integer;
  Thread: TWorker;
  Done: Boolean;
begin
  // 在数组中查找线程
  ThreadIdx := -1;
  for i := Low(FThreads) to High(FThreads) do
    if FThreads[i].Handle = Cardinal(Message.ThreadHandle) then
    begin
      ThreadIdx := i;
      Break;
    end;

  // 报告结果并释放线程,调零其指针和句柄 
  // 这样我们就可以检测到所有线程何时完成。
  if ThreadIdx > -1 then
  begin
    Thread := TWorker(FThreads[i].Instance);
    Memo1.Lines.Add(Format('Thread %d returned %f', [ThreadIdx, Thread.Result]));
    FreeAndNil(FThreads[i].Instance);
    FThreads[i].Handle := nil;
  end;

  // 查看是否所有线程都已完成。
  Done := True;
  for i := Low(FThreads) to High(FThreads) do
    if Assigned(FThreads[i].Instance) then
    begin
      Done := False;
      Break;
    end;
  if Done then
  begin
    Memo1.Lines.Add('Work done');
    FRunning := False;
  end;
end;

此方法首先使用消息中接收到的句柄查找线程。如果找到匹配项,它将使用实例(FreeOnTerminatewas False,还记得吗?)检索并报告线程的结果,然后完成操作:释放实例并将实例引用和句柄都设置为nil,表明该线程不再相关。

最后,它检查是否有任何线程仍在运行。如果未找到,则报告“所有完成”,并将FRunning标志设置为,False以便可以开始新的一批工作。

 类似资料:
  • 问题内容: 我在Qt工作,当我按下GO按钮时,我需要不断将软件包发送到网络并使用收到的信息修改界面。 问题是我在按钮中有一个,所以按钮永不结束,所以界面永不更新。我想在按钮中创建一个线程并将代码放在那里。 我的问题是如何从线程修改接口?(例如,如何从线程修改textBox? 问题答案: 关于Qt的重要一点是, 必须 仅从GUI线程(即主线程)使用Qt GUI。 这就是为什么执行此操作的正确方法是从

  • null 所以你可以不受任何限制地做背景工作。尽管由于这些原因您应该使用服务,但链接。 这是做背景工作的另一种方式(当然不是更好,但仍然是一种方式)吗?我错了吗?

  • 问题内容: 我在Oracle Java教程中遇到了这个示例,该示例描述了多线程场景中的死锁。 因此,在此示例中,我在第17行和第18行进行了以下更改。 完成这些更改后,程序将成功终止,而不会导致死锁,并在输出后进行打印 所以我的问题是-为什么会这样表现?println语句如何防止死锁? 问题答案: 无论您使用还是,都没有什么区别:它们基本上是在做同一件事。 如果在和的开始之间开始执行,则在此处发生

  • 我构建了这个“节流”任务运行器,它在HashMap中收集一些数据,同时(每分钟)将数据“带走”并清除HashMap。在我的测试中,我注意到executor部分可以停止,并且永远不会再次清除HashMap。我假设这是因为我所做的HashMap修改不是线程安全的,它在内部崩溃,没有恢复。我正在两个线程中修改HashMap。有人能告诉我如何优化HashMap修改的正确方向吗。

  • 我想对我的Java-JMeter机器进行线程转储。在我的Jmeter机器中,我可以看到许多线程在测试计时后没有关闭和堆积。为了进行更多的调试,我尝试进行线程转储(使用“jstack-pid>>fileae.txt”命令)。但该命令在24小时后仍在运行,尽管它创建了一个空文本文件。为什么我不能对java进程进行线程转储。

  • 问题内容: 我刚刚为iPhone和新的Xcode下载了iOS 13。我想测试适用于iOS 13的应用程序,但是当我尝试运行应用程序时,几秒钟后会出现错误。错误: 由于未捕获的异常“ NSInternalInconsistencyException”而终止应用程序,原因:“从主线程访问布局引擎后,不得从后台线程对其进行修改。” ***第一掷调用堆栈:(0x191ea9c30 0x191bc40c8