问题描述
我一直想知道有没有更好的方法,我应该写一些我的程序,特别是那些需要很长时间才能完成的程序。我有总是从主GUI线程运行所有的东西,我现在明白并认识到这是坏的,因为它会使应用程序无响应, Application.ProcessMessages
在这里不会真的有帮助。
这使我觉得我需要使用TThreads来进行冗长的操作,例如复制一个文件。这也让我想知道一些应用程序如何为您提供完全控制,例如允许您暂停,恢复和/或停止操作。
我有一个长时间的操作个人项目我正在工作,我显示一个对话窗体与TProgressBar。虽然这样做有效,但我觉得可以做得更好。这些进度对话框可能会显示很长时间,您可能希望取消操作,然后稍后完成此工作。
正如我所说,目前我正在运行主要的线程,我需要使用TThreads吗?我不知道如何或在哪里开始实施它们,因为我以前没有和他们一起工作。如果我需要线程,他们会提供我所需要的,如暂停,恢复,停止操作等?
基本上我正在寻找一种更好的处理和管理冗长的方法
是的,这绝对是您需要线程来完成任务的情况。
一个例子如何暂停/恢复线程并取消线程。
进度通过PostMessage调用发送到主线程。
暂停/恢复和取消使用 TSimpleEvent
信号进行。
编辑: / strong>根据@mghie的评论,这里是一个更完整的例子:
编辑2:显示如何传递一个程序
编辑3:添加了更多功能和测试单元。
unit WorkerThread;
界面
使用Windows,Classes,SyncObjs;
type
TWorkFunction = function:对象的布尔值;
TWorkerThread = Class(TThread)
private
FCancelFlag:TSimpleEvent;
FDoWorkFlag:TSimpleEvent;
FOwnerFormHandle:HWND;
FWorkFunc:TWorkFunction; //函数调用方法
FCallbackMsg:integer; // PostMessage id
FProgress:integer;
procedure SetPaused(doPause:boolean);
函数GetPaused:boolean;
程序执行;覆盖
public
构造函数Create(WindowHandle:HWND; callbackMsg:integer;
myWorkFunc:TWorkFunction);
破坏者破坏;覆盖
函数StartNewWork(newWorkFunc:TWorkFunction):boolean;
属性Paused:boolean读取GetPaused写SetPaused;
结束
实现
构造函数TWorkerThread.Create(WindowHandle:HWND; callbackMsg:integer;
myWorkFunc:TWorkFunction);
begin
继承Create(false);
FOwnerFormHandle:= WindowHandle;
FDoWorkFlag:= TSimpleEvent.Create;
FCancelFlag:= TSimpleEvent.Create;
FWorkFunc:= myWorkFunc;
FCallbackMsg:= callbackMsg;
Self.FreeOnTerminate:= false; //线程破坏的主线程控件
如果分配(FWorkFunc)然后
FDoWorkFlag.SetEvent; //启动工作
end;
析构函数TWorkerThread.Destroy; // Call MyWorkerThread.Free取消线程
begin
FDoWorkFlag.ResetEvent; //停止正在进行的工作
FCancelFlag.SetEvent; //设置取消标志
Waitfor; // Synchronize
FCancelFlag.Free;
FDoWorkFlag.Free;
继承;
结束
程序TWorkerThread.SetPaused(doPause:boolean);
begin
如果doPause然后
FDoWorkFlag.ResetEvent
else
FDoWorkFlag.SetEvent;
结束
函数TWorkerThread.StartNewWork(newWorkFunc:TWorkFunction):boolean;
begin
结果:= Self.Paused; //必须暂停!
如果结果然后
开始
FWorkFunc:= newWorkFunc;
FProgress:= 0; //重新进行计数器
如果已分配(FWorkFunc)然后
FDoWorkFlag.SetEvent; //开始工作
end;
结束
程序TWorkerThread.Execute;
{ - PostMessage LParam:
0:正在进行中,进度计数器在WParam
1:工作就绪
2:线程正在关闭
}
var
readyFlag:boolean;
waitList:数组[0 .. 1]的THandle;
begin
FProgress:= 0;
waitList [0]:= FDoWorkFlag.Handle;
waitList [1]:= FCancelFlag.Handle;
而不是终止do
begin
if(WaitForMultipleObjects(2,@waitList [0],false,INFINITE)
WAIT_OBJECT_0)then
break; //当FCancelFlag发出信号时终止线程
//做一些工作
readyFlag:= FWorkFunc;
如果readyFlag然后//工作完成,暂停线程
Self.Paused:= true;
Inc(FProgress);
//通知主线程关于进度
PostMessage(FOwnerFormHandle,FCallbackMsg,WPARAM(FProgress),
LPARAM(readyFlag));
结束
PostMessage(FOwnerFormHandle,FCallbackMsg,0,LPARAM(2)); //关闭线程
end;
函数TWorkerThread.GetPaused:boolean;
begin
结果:=(FDoWorkFlag.Waitfor(0)<" wrSignaled);
结束
结束。
只需调用 MyThread.Paused:= true
暂停和 MyThread.Paused:= false
恢复线程操作。
要取消线程,请调用 MyThread.Free
。
要从线程接收已发布的消息,请参见以下示例:
$ b单位Unit1;$ b
接口
使用
Winapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,
System.Classes,Vcl.Graphics,
Vcl.Controls,Vcl.Forms,Vcl.Dialogs,Vcl.StdCtrls,WorkerThread;
const
WM_MyProgress = WM_USER + 0; //唯一的消息id
type
TForm1 = class(TForm)
Label1:TLabel;
btnStartTask:TButton;
btnPauseResume:TButton;
btnCancelTask:TButton;
Label2:TLabel;
procedure btnStartTaskClick(Sender:TObject);
procedure btnPauseResumeClick(Sender:TObject);
procedure btnCancelTaskClick(Sender:TObject);
private
{私有声明}
MyThread:TWorkerThread;
workLoopIx:integer;
函数HeavyWork:boolean;
procedure OnMyProgressMsg(var Msg:TMessage);消息WM_MyProgress;
public
{公开声明}
end;
var
Form1:TForm1;
执行
{$ R * .dfm}
{TForm1}
const
cWorkLoopMax = 500;
函数TForm1.HeavyWork:boolean; //准备好时
var
i,j:integer;
begin
j:= 0;
for i:= 0至10000000 do
Inc(j);
Inc(workLoopIx);
结果:=(workLoopIx> = cWorkLoopMax);
结束
程序TForm1.btnStartTaskClick(Sender:TObject);
begin
如果没有分配(MyThread)然后
begin
workLoopIx:= 0;
btnStartTask.Enabled:= false;
btnPauseResume.Enabled:= true;
btnCancelTask.Enabled:= true;
MyThread:= TWorkerThread.Create(Self.Handle,WM_MyProgress,HeavyWork);
结束
结束
程序TForm1.btnPauseResumeClick(Sender:TObject);
begin
如果分配(MyThread)然后
MyThread.Paused:=不是MyThread.Paused;
结束
程序TForm1.btnCancelTaskClick(Sender:TObject);
开始
如果分配(MyThread)然后
开始
FreeAndNil(MyThread);
btnStartTask.Enabled:= true;
btnPauseResume.Enabled:= false;
btnCancelTask.Enabled:= false;
结束
结束
程序TForm1.OnMyProgressMsg(var Msg:TMessage);
begin
Msg.Msg:= 1;
case Msg.LParam
0:
Label1.Caption:=格式('%5.1f %%',[100.0 * Msg.WParam / cWorkLoopMax]);
1:
begin
Label1.Caption:='Task done';
btnCancelTaskClick(Self);
结束
2:
Label1.Caption:=任务终止;
结束
结束
结束。
表单:
对象Form1:TForm1
Left = 0
顶部= 0
Caption ='Form1'
ClientHeight = 163
ClientWidth = 328
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name ='Tahoma'
字体。 Style = []
OldCreateOrder = False
PixelsPerInch = 120
TextHeight = 16
对象Label1:TLabel
左= 79
顶部= 18
宽度= 51
高度= 16
标题='任务空闲'
结束
对象Label2:TLabel
左= 32
顶部= 18
宽度= 41
高度= 16
Caption ='状态:'
结束
对象btnStartTask:TButton
左= 32
顶部= 40
宽度= 137
高度= 25
Caption ='开始'
TabOrder = 0
OnClick = btnStartTaskClick
end
对象btn PauseResume:TButton
Left = 32
顶部= 71
宽度= 137
高度= 25
Caption ='暂停/恢复'
启用= False
TabOrder = 1
OnClick = btnPauseResumeClick
end
对象btnCancelTask:TButton
Left = 32
顶部= 102
宽度= 137
Height = 25
Caption ='取消'
启用= False
TabOrder = 2
OnClick = btnCancelTaskClick
end
end
I've always wondered is there a better way that I should be writing some of my procedures, particularly ones that take a long time to finish.
I have always run everything off the Main GUI Thread which I now understand and realise is bad because it will make the Application unresponsive, Application.ProcessMessages
will not really help here.
This makes me think I need to use TThreads for lengthy operations such as copying a file for example. This is also made me wonder how some Applications give you full control, eg allow you to pause, resume and or stop the operation.
I have about 3 lengthy operations in a personal project I am working on which I display a dialog form with a TProgressBar on. Whilst this does work, I feel it could be done much better. These progress dialogs could be shown for such a long time that you may want to cancel the operation and instead finish the job later.
As I said, currently I am running of the Main Gui Thread, do I instead need to use TThreads? I am not sure how or where to start implementing them as I have not worked with them before. If I do need threads do they offer what I need such as pausing, resuming, stopping an operation etc?
Basically I am looking for a better way of handling and managing lengthy operations.
Yes, this is definitely a case where you need a thread to do the task.
A little example how to pause/resume a thread and cancel the thread.
Progress is sent to the main thread through a PostMessage call.The pause/resume and cancel are made with TSimpleEvent
signals.
Edit: As per the comments from @mghie, here is a more complete example:
Edit 2: Showing how to pass a procedure for the thread to call for the heavy work.
Edit 3: Added some more features and a test unit.
unit WorkerThread;
interface
uses Windows, Classes, SyncObjs;
type
TWorkFunction = function: boolean of object;
TWorkerThread = Class(TThread)
private
FCancelFlag: TSimpleEvent;
FDoWorkFlag: TSimpleEvent;
FOwnerFormHandle: HWND;
FWorkFunc: TWorkFunction; // Function method to call
FCallbackMsg: integer; // PostMessage id
FProgress: integer;
procedure SetPaused(doPause: boolean);
function GetPaused: boolean;
procedure Execute; override;
public
Constructor Create(WindowHandle: HWND; callbackMsg: integer;
myWorkFunc: TWorkFunction);
Destructor Destroy; override;
function StartNewWork(newWorkFunc: TWorkFunction): boolean;
property Paused: boolean read GetPaused write SetPaused;
end;
implementation
constructor TWorkerThread.Create(WindowHandle: HWND; callbackMsg: integer;
myWorkFunc: TWorkFunction);
begin
inherited Create(false);
FOwnerFormHandle := WindowHandle;
FDoWorkFlag := TSimpleEvent.Create;
FCancelFlag := TSimpleEvent.Create;
FWorkFunc := myWorkFunc;
FCallbackMsg := callbackMsg;
Self.FreeOnTerminate := false; // Main thread controls for thread destruction
if Assigned(FWorkFunc) then
FDoWorkFlag.SetEvent; // Activate work at start
end;
destructor TWorkerThread.Destroy; // Call MyWorkerThread.Free to cancel the thread
begin
FDoWorkFlag.ResetEvent; // Stop ongoing work
FCancelFlag.SetEvent; // Set cancel flag
Waitfor; // Synchronize
FCancelFlag.Free;
FDoWorkFlag.Free;
inherited;
end;
procedure TWorkerThread.SetPaused(doPause: boolean);
begin
if doPause then
FDoWorkFlag.ResetEvent
else
FDoWorkFlag.SetEvent;
end;
function TWorkerThread.StartNewWork(newWorkFunc: TWorkFunction): boolean;
begin
Result := Self.Paused; // Must be paused !
if Result then
begin
FWorkFunc := newWorkFunc;
FProgress := 0; // Reset progress counter
if Assigned(FWorkFunc) then
FDoWorkFlag.SetEvent; // Start work
end;
end;
procedure TWorkerThread.Execute;
{- PostMessage LParam:
0 : Work in progress, progress counter in WParam
1 : Work is ready
2 : Thread is closing
}
var
readyFlag: boolean;
waitList: array [0 .. 1] of THandle;
begin
FProgress := 0;
waitList[0] := FDoWorkFlag.Handle;
waitList[1] := FCancelFlag.Handle;
while not Terminated do
begin
if (WaitForMultipleObjects(2, @waitList[0], false, INFINITE) <>
WAIT_OBJECT_0) then
break; // Terminate thread when FCancelFlag is signaled
// Do some work
readyFlag := FWorkFunc;
if readyFlag then // work is done, pause thread
Self.Paused := true;
Inc(FProgress);
// Inform main thread about progress
PostMessage(FOwnerFormHandle, FCallbackMsg, WPARAM(FProgress),
LPARAM(readyFlag));
end;
PostMessage(FOwnerFormHandle, FCallbackMsg, 0, LPARAM(2)); // Closing thread
end;
function TWorkerThread.GetPaused: boolean;
begin
Result := (FDoWorkFlag.Waitfor(0) <> wrSignaled);
end;
end.
Just call MyThread.Paused := true
to pause and MyThread.Paused := false
to resume the thread operation.
To cancel the thread, call MyThread.Free
.
To receive the posted messages from the thread, see following example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, WorkerThread;
const
WM_MyProgress = WM_USER + 0; // The unique message id
type
TForm1 = class(TForm)
Label1: TLabel;
btnStartTask: TButton;
btnPauseResume: TButton;
btnCancelTask: TButton;
Label2: TLabel;
procedure btnStartTaskClick(Sender: TObject);
procedure btnPauseResumeClick(Sender: TObject);
procedure btnCancelTaskClick(Sender: TObject);
private
{ Private declarations }
MyThread: TWorkerThread;
workLoopIx: integer;
function HeavyWork: boolean;
procedure OnMyProgressMsg(var Msg: TMessage); message WM_MyProgress;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
const
cWorkLoopMax = 500;
function TForm1.HeavyWork: boolean; // True when ready
var
i, j: integer;
begin
j := 0;
for i := 0 to 10000000 do
Inc(j);
Inc(workLoopIx);
Result := (workLoopIx >= cWorkLoopMax);
end;
procedure TForm1.btnStartTaskClick(Sender: TObject);
begin
if not Assigned(MyThread) then
begin
workLoopIx := 0;
btnStartTask.Enabled := false;
btnPauseResume.Enabled := true;
btnCancelTask.Enabled := true;
MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, HeavyWork);
end;
end;
procedure TForm1.btnPauseResumeClick(Sender: TObject);
begin
if Assigned(MyThread) then
MyThread.Paused := not MyThread.Paused;
end;
procedure TForm1.btnCancelTaskClick(Sender: TObject);
begin
if Assigned(MyThread) then
begin
FreeAndNil(MyThread);
btnStartTask.Enabled := true;
btnPauseResume.Enabled := false;
btnCancelTask.Enabled := false;
end;
end;
procedure TForm1.OnMyProgressMsg(var Msg: TMessage);
begin
Msg.Msg := 1;
case Msg.LParam of
0:
Label1.Caption := Format('%5.1f %%', [100.0 * Msg.WParam / cWorkLoopMax]);
1:
begin
Label1.Caption := 'Task done';
btnCancelTaskClick(Self);
end;
2:
Label1.Caption := 'Task terminated';
end;
end;
end.
And the form:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 163
ClientWidth = 328
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 120
TextHeight = 16
object Label1: TLabel
Left = 79
Top = 18
Width = 51
Height = 16
Caption = 'Task idle'
end
object Label2: TLabel
Left = 32
Top = 18
Width = 41
Height = 16
Caption = 'Status:'
end
object btnStartTask: TButton
Left = 32
Top = 40
Width = 137
Height = 25
Caption = 'Start'
TabOrder = 0
OnClick = btnStartTaskClick
end
object btnPauseResume: TButton
Left = 32
Top = 71
Width = 137
Height = 25
Caption = 'Pause/Resume'
Enabled = False
TabOrder = 1
OnClick = btnPauseResumeClick
end
object btnCancelTask: TButton
Left = 32
Top = 102
Width = 137
Height = 25
Caption = 'Cancel'
Enabled = False
TabOrder = 2
OnClick = btnCancelTaskClick
end
end
这篇关于我需要TThreads吗?如果是这样,我可以暂停,恢复并停止他们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!