我的TCustomControl后代使用线程,这涉及使用InvalidateRect进行无效。我遇到了当我在线程工作时关闭程序时我不会停止Destroy中的线程,因为即使在我进入组件的Destroy的第一行(我通常发出信号并等待线程停止工作)之前,来自线程的代码使得程序在Handle请求上可能显示异常“Control ...没有父窗口”。
答案 0 :(得分:4)
所有窗口交互 - 就像对InvalidateRect
的调用 - 应该在主线程中完成,所以从支持到主线程同步它或者去掉额外的线程。
其次,当一个控件被销毁时,你不能再使用它的窗口句柄,因为它可能已经消失了。在这种情况下,VCL将尝试再次重新创建句柄,尽管控件正在被销毁,从而导致各种错误。如果您显然或必须具有可能在控件销毁期间调用的绘图指令,那么请对该代码进行此检查:
if not (csDestroying in ComponentState) then
这段代码当然也必须在主线程中! (不是因为窗口句柄已经被破坏,而是因为VCL不是线程安全的)。这是隐含的,因为下面的代码 - 绘画 - 也是(现在)在主线程中。
答案 1 :(得分:1)
从线程访问TWinControl.Handle
属性时,这是一个标准错误 - 在线程的上下文中重新创建基础窗口。
严格地说,从一个线程访问TWinControl.Handle
是不安全的,尽管问题通常出现在某些极端情况下,例如关闭一个应用程序。
TWinControl
类还提供受保护的WindowHandle
属性。您可以在不强制创建窗口句柄的情况下读取该值。如果您的组件派生自TWinControl
(或TCustomControl
),则可以从线程访问它。
答案 2 :(得分:0)
接受的答案仍然有意义,但我刚刚找到了一个对多线程和VCL(TVCLMutex)有用的对象,所以它部分回答了我自己的问题。诀窍是我在主线程中创建另一个窗口,因此它共享相同的消息队列。从那以后,等待它自己的消息使得VCL神奇地安全,至少在大多数情况下(因为所有代码直接和间接地来自同一队列)。因此,如果您的线程想要在VCL中访问/更改某些内容,请创建一个TVCLMutex实例并在其Access / Release中包装不安全的访问代码,您可以确定主线程在Windows核心中的某个位置等待继续处理消息而无处可去其他。
unit VclMutex;
interface uses Windows, Messages, SysUtils;
const
WM_WaitInMainThread = WM_USER + 1;
type
TVCLMutex = class
private
fVCLWaitEvent: THandle;
fAccessAvailEvent: THandle;
fWnd: HWnd;
fThreadId: Cardinal;
fLevel: integer;
protected
procedure WndProc(var Msg: TMessage);
public
constructor Create;
destructor Destroy;override;
procedure Access;
procedure Release;
end;
implementation uses Forms;
{ TVCLMutex }
constructor TVCLMutex.Create;
begin
inherited Create;
if GetCurrentThreadId <> MainThreadId then
raise Exception.Create('The object should be created in the main thread');
fWnd:=AllocateHWnd(WndProc);
fVCLWaitEvent:=CreateEvent(Nil, false, false, Nil);
fAccessAvailEvent:=CreateEvent(Nil, false, true, Nil);
end;
destructor TVCLMutex.Destroy;
begin
CloseHandle(fVCLWaitEvent);
CloseHandle(fAccessAvailEvent);
DeallocateHWnd(fWnd);
inherited;
end;
procedure TVCLMutex.WndProc(var Msg: TMessage);
begin
case Msg.Msg of
WM_WaitInMainThread:
begin
{ ReplyMessage make return from SendMessage call called from other thread }
ReplyMessage(0);
{ Waiting to release VCL (main thread) queure. Signal will come from the
Release method of working thread }
WaitForSingleObject(fVCLWaitEvent, INFINITE);
end
else
Msg.Result := DefWindowProc(FWnd, Msg.Msg, Msg.wParam, Msg.lParam);
end;
end;
procedure TVCLMutex.Access;
var
AThreadId: Cardinal;
begin
AThreadId:=GetCurrentThreadId();
if GetCurrentThreadId = fThreadId then
begin
{ If owning thread then allow while keeping nested level }
Inc(fLevel);
end
else
begin
{ Waiting for section to be available (from other threads) }
WaitForSingleObject(fAccessAvailEvent, INFINITE);
{ Owning thread }
fThreadId:=AThreadId;
{ Ask main thread queue to wait }
if fThreadId <> MainThreadId then
SendMessage(fWnd, WM_WaitInMainThread, 0, 0);
fLevel:=1;
end
end;
procedure TVCLMutex.Release;
begin
Dec(fLevel);
if fLevel = 0 then { All nested releases passed }
begin
{ Signaling for queue to release (if different thread) }
if fThreadId <> MainThreadId then
SetEvent(fVCLWaitEvent);
fThreadId:=0;
{ Signaling that the mutex section is available }
SetEvent(fAccessAvailEvent);
end;
end;
end.