我们的应用程序处理测量数据,测量数据必须从测量设备读取并存储在数据库中。
我们提供批量阅读和阅读的选项。一次存储多组测量数据。因为这是一个耗时的过程,我们会显示一个模式对话框,其中包含一个进度条和一个取消操作的按钮。
只有在读取并存储完整的测量数据集后才能取消操作。
阅读& store循环如下:
ItemsToStore := GetSelectedTreeItems();
DlgProgress := TProgressWithAbort.Create(Screen.ActiveForm);
try
for i := 0 to Pred(ItemsToStore.Count) do
begin
if DlgProgress.Cancel then exit;
DlgProgress.Description := ItemsToStore[i].Name;
ReadAndStoreItem(ItemsToStore[i].Id);
DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
end;
finally
DlgProgress.Free;
end;
进度对话框的Position属性的setter调用一个名为CheckMouseButtonInput的过程,该过程当前编码如下:
procedure TProgressWithAbort.CheckMouseButtonInput;
var
Msg: TMsg;
begin
// if the left mouse button was pressed while the mouse was at the
// Cancel button call the application's message loop to process the event
if PeekMessage(Msg, btnCancel.Handle, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE) then
Application.ProcessMessages;
end;
从应用程序的消息循环中调用以下按钮单击处理程序,它设置可通过属性访问的变量取消:
procedure TProgressWithAbort.btnCancelClick(Sender: TObject);
begin
FCancel := true;
end;
一切正常。但我想知道CheckMouseButtonInput的上述实现是否会占用过多的CPU时间。在GetQueueStatus
之前调用MsgWaitForMultipleObjects
或PeekMessage
(没有句柄和超时为0)会更好吗?
答案 0 :(得分:1)
因为这是一个耗时的过程,我们会显示一个带有进度条的模态对话框和一个取消操作的按钮。
然后应该将进程移动到单独的工作线程。不要在主UI线程中运行冗长的操作。它应该只处理UI而不是其他任何东西。即使你想要阻止主UI线程直到进程完成,你仍然应该让主线程正常处理消息,不要手动完成。
启动线程,显示对话框,如果按下取消按钮则发出线程终止信号,并在线程退出时关闭对话框。线程可以在需要时向对话框发送UI更新,并检查测量之间的终止状态。不需要CheckMouseButtonInput()
逻辑。
例如:
type
TCancelEvent = procedure of object;
TProgressWithAbort = class(TForm)
btnCancel: TButton;
procedure btnCancelClick(Sender: TObject);
private
FCancel: Boolean;
FOnCancel: TCancelEvent;
public
property Cancel: Boolean read FCancel;
property OnCancel: TCancelEvent read FOnCancel write FOnCancel;
end;
procedure TProgressWithAbort.btnCancelClick(Sender: TObject);
begin
FCancel := true;
if Assigned(FOnCancel) then
FOnCancel();
end;
procedure TMyForm.LengthyProcess;
var
ItemsToStore: TListOfWhatever;
StoreThread: TThread;
DlgProgress: TProgressWithAbort;
begin
ItemsToStore := GetSelectedTreeItems();
DlgProgress := TProgressWithAbort.Create(Self);
try
StoreThread := TThread.CreateAnonymousThread(
procedure
var
i: Integer;
begin
try
for i := 0 to Pred(ItemsToStore.Count) do
begin
if TThread.CheckTerminated then Exit;
TThread.Queue(TThread.CurrentThread,
procedure
begin
DlgProgress.Description := ItemsToStore[i].Name;
end;
);
// make sure this function is thread-safe!
ReadAndStoreItem(ItemsToStore[i].Id);
TThread.Queue(TThread.CurrentThread,
procedure
begin
DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
end
);
end;
finally
DlgProgress.ModalResult := mrClose;
end;
end
);
try
StoreThread.FreeOnTerminate := False;
StoreThread.Start;
try
DlgProgress.OnCancel := StoreThread.Terminate;
DlgProgress.ShowModal;
finally
StoreThread.Terminate;
StoreThread.WaitFor;
end;
finally
StoreThread.Free;
end;
finally
DlgProgress.Free;
end;
end;
可替换地:
var
ItemsToStore: TListOfWhatever;
StoreThread: TThread;
DlgProgress: TProgressWithAbort;
...
procedure TMyForm.StartLengthyProcess;
begin
ItemsToStore := GetSelectedTreeItems();
StoreThread := TThread.CreateAnonymousThread(
procedure
var
i: Integer;
begin
for i := 0 to Pred(ItemsToStore.Count) do
begin
if TThread.CheckTerminated then Exit;
TThread.Queue(TThread.CurrentThread,
procedure
begin
DlgProgress.Description := ItemsToStore[i].Name;
end;
);
// make sure this function is thread-safe!
ReadAndStoreItem(ItemsToStore[i].Id);
TThread.Queue(TThread.CurrentThread,
procedure
begin
DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
end
);
end;
end
);
StoreThread.OnTerminate := LengthyProcessFinished;
DlgProgress := TProgressWithAbort.Create(Self);
DlgProgress.OnCancel := StoreThread.Terminate;
DlgProgress.Show;
// disable the rest of the UI as needed..
StoreThread.Start;
end;
procedure TMyForm.LengthyProcessFinished(Sender: TObject);
begin
StoreThread := nil;
FreeAndNil(DlgProgress);
// enable the rest of the UI as needed..
end;