要在长时间运行过程中使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