delphi - 使用此表单的句柄在表单/框架的 OnCreate 中启动线程
问题描述
我有一个问题,我不知道如何解决。
我尝试在OnCreate
事件中启动一个线程,或者在创建一个TFrame
when it Parent
is still之后启动一个线程nil
。创建线程时,我向它传递了一个窗口句柄,但是窗口的地址在OnShow
事件之后发生了变化。
procedure Form1.OnCreate(Sender: TObject);
begin
TCustomThread.Create(Self);
Label1.Caption := IntToStr(Self.Handle); //for example 10203040
end;
procedure Form1.ButtonOnClick;
begin
Label1.Caption := IntToStr(Self.Handle); //i give 342545454 not 10203040
end;
procedure Form1.FromThread(var Msg: TMessage); message WM_TheardComplete;
begin
{do something}
end;
constructor TCustomThread.Create(AWinControl: TWinControl);
begin
inherited Create(False);
FWinControl := AWinControl;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWinControl.Handle, WM_TheardComplete, 0, 0); //Handle 10203040
end;
我可以使用什么参数来启动线程,以便以后可以向该对象发送消息?
解决方案
该TWinControl.Handle
属性不是线程安全的。VCL 可以并且确实在控件的生命周期内动态地重新创建控件的窗口,甚至多次。但更重要的是,窗口具有线程亲和性,给定窗口的消息检索和处理仅在创建窗口的线程中工作。使用控件Handle
属性的工作线程会导致竞争条件,如果您不小心,实际上可能会导致工作线程捕获控件窗口的所有权,从而使控件在主 UI 线程中完全无用。
如果您需要为工作线程提供一个窗口来发布/发送消息,请为线程提供一个VCL 不会破坏(无需您告诉它)的持久窗口,例如通过使用主TApplication
窗口,使用其OnMessage
事件处理已发布的消息,或其HookMainWindow()
处理已发送消息的方法,例如:
procedure Form1.OnCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
TCustomThread.Create(Application.Handle);
end;
procedure Form1.OnDestroy(Sender: TObject);
begin
Application.OnMessage := nil;
end;
procedure Form1.AppMessage(var Msg: tagMSG; var Handled: Boolean);
begin
if Msg.message = WM_TheardComplete then
begin
Handled := True;
{do something}
end;
end;
constructor TCustomThread.Create(AWnd: HWND);
begin
inherited Create(False);
FWnd := AWnd;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;
或者更好的是,使用使用 VCL 功能创建的新专用窗口 AllocateHWnd()
,例如:
procedure Form1.OnCreate(Sender: TObject);
begin
ThreadWnd := AllocateHWnd(ThreadWndProc);
TCustomThread.Create(ThreadWnd);
end;
procedure Form1.OnDestroy(Sender: TObject);
begin
DeallocateHWnd(ThreadWnd);
end;
procedure Form1.ThreadWndProc(var Message: TMessage);
begin
if Message.Msg = WM_TheardComplete then
begin
{do something}
end else
Message.Result := DefWindowProc(ThreadWnd, Message.Msg, Message.WParam, Message.LParam);
end;
constructor TCustomThread.Create(AWnd: HWND);
begin
inherited Create(False);
FWnd := AWnd;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;
但是,在您提供的示例中,我建议使用完全不同的方法,而不是在线程执行结束时发送消息 - 使用TThread.OnTerminate
已经与主线程同步的事件,例如:
procedure Form1.OnCreate(Sender: TObject);
var
Thread: TCustomThread;
begin
Thread := TCustomThread.Create;
Thread.OnTerminate := ThreadFinished;
Thread.Start; // or Resume() in older versions
end;
procedure Form1.ThreadFinished(Sender: TObject);
begin
{do something}
end;
constructor TCustomThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
end;
或者,在现代版本的 Delphi 中,考虑TThread.CreateAnonymousThread()
改用,例如:
procedure Form1.OnCreate(Sender: TObject);
var
Thread: TThread;
begin
Thread := TThread.CreateAnonymousThread(
procedure
begin
{do something}
end
);
Thread.OnTerminate := ThreadFinished;
Thread.Start;
end;
procedure Form1.ThreadFinished(Sender: TObject);
begin
{do something}
end;
甚至:
procedure Form1.OnCreate(Sender: TObject);
begin
TThread.CreateAnonymousThread(
procedure
begin
try
{do something}
finally
TThread.Queue(nil,
procedure
begin
{do something}
end
);
end;
end
).Start;
end;
推荐阅读
- angular - 可以将 XSRF-TOKEN 在标头中从后端发送到前端而不是将其存储为 cookie 吗?
- c - 为什么我在 gdb 回溯中看不到行号?
- powershell - Powershell“不支持UNC路径”但我正在使用Push-Location
- c++ - 如何将赫兹数量“转换”为适当的字节/位格式?
- c# - 如何将 Json 格式化程序添加到 MvcCore?
- authentication - 用户密码更改时使 IdentityServer 会话和访问令牌失效
- pyspark - ImportError:无法从“graphframes.lib”导入名称“Pregel”
- postgresql - 将 EntityFramework Core 迁移到另一个项目后,他们停止工作
- c - 防止 malloc 函数包装
- android - 添加 androidx.fragment:fragment-testing 依赖项后,AndroidTests 停止构建