原始问题
在我们的 Delphi XE4 应用程序中,我们使用 TOmniEventMonitor 来接收来自其他任务的消息。只要它在主线程中运行,它就可以正常工作,但是一旦我将相同的代码放入任务中,TOmniEventMonitor 就会停止接收消息。我在下面包含了一个简单的示例——单击 Button_TestInMainThread 会导致按预期写入文件,单击 Button_TestInBackgroundThread 不会。这是设计使然,还是有某种方法可以在仍然使用 TOmniEventMonitor 的同时使其工作?
unit mainform;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics,Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
OtlTask, OtlTaskControl, OtlComm, OtlEventMonitor;
const
MY_OMNI_MESSAGE = 134;
type
TOmniEventMonitorTester = class(TObject)
fName : string;
fOmniEventMonitor : TOmniEventMonitor;
fOmniTaskControl : IOmniTaskControl;
constructor Create(AName : string);
destructor Destroy(); override;
procedure HandleOmniTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
end;
TTestLauncherTask = class(TOmniWorker)
fOmniTaskMonitorTester : TOmniEventMonitorTester;
function Initialize() : boolean; override;
end;
TForm1 = class(TForm)
Button_TestInMainThread: TButton;
Button_TestInBackgroundThread: TButton;
procedure Button_TestInMainThreadClick(Sender: TObject);
procedure Button_TestInBackgroundThreadClick(Sender: TObject);
private
fOmniEventMonitorTester : TOmniEventMonitorTester;
fTestLauncherTask : IOmniTaskControl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure OmniTaskProcedure_OneShotTimer(const task: IOmniTask);
begin
Sleep(1000);
task.Comm.Send(MY_OMNI_MESSAGE);
end;
constructor TOmniEventMonitorTester.Create(AName : string);
begin
inherited Create();
fName := AName;
fOmniEventMonitor := TOmniEventMonitor.Create(nil);
fOmniEventMonitor.OnTaskMessage := HandleOmniTaskMessage;
fOmniTaskControl := fOmniEventMonitor.Monitor(CreateTask(OmniTaskProcedure_OneShotTimer)).Run();
end;
destructor TOmniEventMonitorTester.Destroy();
begin
fOmniEventMonitor.Free();
inherited Destroy();
end;
procedure TOmniEventMonitorTester.HandleOmniTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
var
Filename : string;
F : TextFile;
begin
Filename := IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0))) + fName + '.txt';
AssignFile(F, Filename);
Rewrite(F);
Writeln(F, fName);
CloseFile(F);
end;
function TTestLauncherTask.Initialize() : boolean;
begin
result := inherited Initialize();
if result then begin
fOmniTaskMonitorTester := TOmniEventMonitorTester.Create('background');
end;
end;
procedure TForm1.Button_TestInMainThreadClick(Sender: TObject);
begin
fOmniEventMonitorTester := TOmniEventMonitorTester.Create('main');
end;
procedure TForm1.Button_TestInBackgroundThreadClick(Sender: TObject);
begin
fTestLauncherTask := CreateTask(TTestLauncherTask.Create()).Run();
end;
end.
其他观察
使用以下代码似乎可以在后台线程中成功使用 TOmniEventMonitor。这确实是一个非常笨拙的解决方案——创建了一个 IOmniTwoWayChannel,但没有以任何有意义的方式使用——但是一旦我尝试通过注释掉标记为“不要删除!”的任何一行来简化代码,HandleTaskMessage 就不会不再被召唤。谁能告诉我我在这里做错了什么?
unit mainform;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
DSiWin32, GpLists, OtlTask, OtlTaskControl, OtlCommon, OtlComm, OtlEventMonitor;
const
MY_OMNI_MESSAGE = 134;
type
TOmniEventMonitorTestTask = class(TOmniWorker)
fOmniTaskControl : IOmniTaskControl;
fOmniTwoWayChannel : IOmniTwoWayChannel;
fOmniEventMonitor : TOmniEventMonitor;
function Initialize() : boolean; override;
procedure HandleTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
procedure HandleTaskTerminated(const task: IOmniTaskControl);
end;
TForm1 = class(TForm)
Button_TestInBackgroundThread: TButton;
procedure Button_TestInBackgroundThreadClick(Sender: TObject);
private
fTestTask : IOmniTaskControl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure OmniTaskProcedure_OneShotTimer(const task: IOmniTask);
begin
Sleep(1000);
task.Comm.Send(MY_OMNI_MESSAGE); // don't remove!
(task.Param['Comm'].AsInterface as IOmniCommunicationEndpoint).Send(MY_OMNI_MESSAGE);
end;
procedure TOmniEventMonitorTestTask.HandleTaskMessage(const task: IOmniTaskControl; const msg: TOmniMessage);
var
Filename : string;
F : TextFile;
begin
Filename := IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0))) + 'HandleTaskMessage.txt';
AssignFile(F, Filename);
Rewrite(F);
Writeln(F, 'HandleTaskMessage!');
CloseFile(F);
end;
procedure TOmniEventMonitorTestTask.HandleTaskTerminated(const task: IOmniTaskControl);
var
Filename : string;
F : TextFile;
begin
Filename := IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0))) + 'HandleTaskTerminated.txt';
AssignFile(F, Filename);
Rewrite(F);
Writeln(F, 'HandleTaskTerminated!');
CloseFile(F);
end;
function TOmniEventMonitorTestTask.Initialize() : boolean;
begin
result := inherited Initialize();
if result then begin
fOmniEventMonitor := TOmniEventMonitor.Create(nil);
fOmniEventMonitor.OnTaskMessage := HandleTaskMessage;
fOmniEventMonitor.OnTaskTerminated := HandleTaskTerminated;
fOmniTwoWayChannel := CreateTwoWayChannel();
Task.RegisterComm(fOmniTwoWayChannel.Endpoint1); // don't remove!
fOmniTaskControl := fOmniEventMonitor.Monitor( CreateTask(OmniTaskProcedure_OneShotTimer) ).SetParameter('Comm', fOmniTwoWayChannel.Endpoint2).Run();
end;
end;
procedure TForm1.Button_TestInBackgroundThreadClick(Sender: TObject);
begin
fTestTask := CreateTask(TOmniEventMonitorTestTask.Create()).Run();
end;
end.
最佳答案
TOmniEventMonitor 在线程内部运行没有问题,只要有一个消息泵处理它的消息。我把这块代码放在一起来演示。这按预期工作。
procedure TMyThread.Execute;
var
Message: TMsg;
begin
FreeOnTerminate := True;
fOmniEventMonitor := TOmniEventMonitor.Create(nil);
fOmniEventMonitor.OnTaskMessage := HandleOmniTaskMessage;
fOmniTaskControl := fOmniEventMonitor.Monitor(CreateTask(OmniTaskProcedure_OneShotTimer)).Run();
try
while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLINPUT) = WAIT_OBJECT_0 then
begin
while PeekMessage(Message, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Message);
DispatchMessage(Message);
end;
end;
end;
finally
fOmniTaskControl := nil;
fOmniEventMonitor.Free;
end;
end;
据我所知, TOmniTaskExecutor 等待特定句柄的消息。在您的代码示例中,它是终止事件和几个通信句柄。永远不会处理 TOmniEventMonitor 的消息。
将
TTestLauncherTask.Initialize
更改为以下结果会正确写出文件。 DoNothingProc
只是类上的一个空方法。function TTestLauncherTask.Initialize() : boolean;
begin
result := inherited Initialize();
if result then begin
fOmniTaskMonitorTester := TOmniEventMonitorTester.Create('background');
// Tell the task about the event monitor
Task.RegisterWaitObject(fOmniTaskMonitorTester.fOmniEventMonitor.MessageWindow, DoNothingProc);
end;
end;
我正在将 TOmniEventMonitor 的消息窗口添加到 Task WaitObject 列表,以便将句柄注册到
MsgWaitForMultipleObjectsEx
调用并等待 Remi 和 David 将我的消息处理撕成碎片:)关于multithreading - TOmniEventMonitor 可以在后台线程中使用吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23987146/