Reputation: 4840
In my application I use thread based tasks. They work fine but sometimes they hang the application. In the code below procedure Stop
sometimes hangs in the WaitFor
procedure. That is because the FStopEvent.SetEvent
not always appears to function.
During a normal execution the thread enters the Execute
procedure, executes the OnWork
procedure until Stop
is called (which sets Terminated
), then does some postprocessing and then exits. That is the signal for WaitFor
to quit and everybody is happy. In my usage this occurs because the task is destroyed. In that case the destructor of the base class is called which calls Stop
.
In some cases this does not work. Execute
is entered correctly, the OnWork
procedure calls are executed ok but there is no reaction on the FStopEvent.SetEvent
. There has been no crash (the statement at the except
is not executed) just nothing. The program hangs because the WaitFor does not return. With debug DCU's I can trace this back to the WaitFor
in unit Classes
where the program hangs at WaitForSingleObject(H[0], INFINITE);
. The OnWork
callback is the same.
The OnBeforeWork and OnAfterWork procedures are nil. MaxLoops = -1
and FreeOnTerminate = False
. I am quite desperate, hope that somebody has a way out.
EDIT 1: The WaitFor
I am talking about occurs in class TEvent_Driven_Task
listed below. Because this class is derived from class TSimple_Task
I have added this class for completeness.
EDIT 2: Application.ProcessMessages
has been removed from TSimple_Task.Stop
as Marjan Venema remarked that this might cause a problem. The outcomes are identical (the program hangs in the WaitFor
).
unit Parallel_Event_Task;
interface
uses Forms, Windows, Classes, SysUtils, SyncObjs,
Parallel_Simple_Task;
type
TEvent_Driven_Task = class (TSimple_Task)
private
FWorkEvent: TEvent; // Event signalling that some work should be done
public
constructor Create (work: TNotifyEvent; CreateSuspended: boolean = False;
max: Int32 = 1;
before: TNotifyEvent = nil; after: TNotifyEvent = nil;
terminate: boolean = True; task: integer = 1); override;
destructor Destroy; override;
procedure Activate (work: TNotifyEvent = nil);
procedure Execute; override;
procedure Stop; override;
procedure Release; override;
end; // Class: TEvent_Driven_Task //
implementation
constructor TEvent_Driven_Task.Create
(
work: TNotifyEvent; // Work to do in Execute loop
CreateSuspended: boolean = False; // False = start now, True = use Start
max: Int32 = 1; // Max loops of Execute loop, negative = infinite loop
before: TNotifyEvent = nil;// Called before Execute loop
after: TNotifyEvent = nil; // Called after Execute loop
terminate: boolean = True; // When true free the task on termination
task: integer = 1 // Task ID
);
begin
inherited Create (work, CreateSuspended, max, before, after, terminate, task);
FWorkEvent := TEvent.Create (nil, False, False, '');
end; // Create //
Destructor TEvent_Driven_Task.Destroy;
begin
inherited Destroy;
end; // Destroy //
procedure TEvent_Driven_Task.Activate (work: TNotifyEvent = nil);
begin
if Assigned (work) then OnWork := work;
FWorkEvent.SetEvent;
end; // Activate //
// Execute calls event handler OnWork in a while loop.
// Before the loop is entered, OnBeforeWork is executed, after: OnAfterWork.
procedure TEvent_Driven_Task.Execute;
var two: TWOHandleArray;
pwo: PWOHandleArray;
ret: DWORD;
begin
pwo := @two;
pwo [0] := FWorkEvent.Handle;
pwo [1] := FStopEvent.Handle;
NameThreadForDebugging (AnsiString (FTaskName));
FLoop := 0;
try
if Assigned (OnBeforeWork) then OnBeforeWork (Self);
while (not Terminated) and (Loop <> Max_Loops) do
begin
FLoop := FLoop + 1;
ret := WaitForMultipleObjects (2, pwo, FALSE, INFINITE);
if ret = WAIT_FAILED then Break;
case ret of
WAIT_OBJECT_0 + 0: if Assigned (OnWork) then OnWork (Self);
WAIT_OBJECT_0 + 1: Terminate;
end; // case
end; // while
if Assigned (OnAfterWork) then OnAfterWork (Self);
// Intercept and ignore the interruption but keep the message
except
on e: exception do FError_Mess := e.Message;
end; // try..except
end; // Execute //
procedure TEvent_Driven_Task.Stop;
begin
Terminate;
FStopEvent.SetEvent;
if not FreeOnTerminate
then WaitFor;
end; // Stop //
procedure TEvent_Driven_Task.Release;
begin
inherited Release;
FWorkEvent.Free;
end; // Release //
end. // Unit: Parallel_Simple_Task //
============= Base class =======================
unit Parallel_Simple_Task;
interface
uses Windows, Classes, SysUtils, SyncObjs, Forms;
type
TSimple_Task = class (TThread)
protected
FStopEvent: TEvent; // Event signalling that the thread has to terminate, set by Stop
FTaskID: integer; // Task sequence number
FTaskName: string; // Task name
FLoop: integer; // Indicates number of times Work has been processed
FMax_Loops: integer; // Maximum # of iterations
FError_Mess: string; // Error message if an exception occurred, else empty
FOnBeforeWork: TNotifyEvent; // Event to be called just before thread loop is entered
FOnWork: TNotifyEvent; // Event caled in Execute loop
FOnAfterWork: TNotifyEvent; // Event to be called just after thread loop is finished
procedure set_name (value: string);
public
constructor Create (work: TNotifyEvent; CreateSuspended: boolean = False; max: Int32 = 1;
before: TNotifyEvent = nil; after: TNotifyEvent = nil;
terminate: boolean = True; task: integer = 1); reintroduce; virtual;
destructor Destroy; override;
procedure Execute; override;
procedure Stop; virtual;
procedure Release; virtual;
property TaskID: integer read FTaskID;
property TaskName: string read FTaskName write set_name;
property Loop: integer read FLoop;
property Max_Loops: integer read FMax_Loops write FMax_Loops;
property OnBeforeWork: TNotifyEvent read FOnBeforeWork write FOnBeforeWork;
property OnWork: TNotifyEvent read FOnWork write FOnWork;
property OnAfterWork: TNotifyEvent read FOnAfterWork write FOnAfterWork;
end; // Class: TSimple_Task //
implementation
constructor TSimple_Task.Create
(
work: TNotifyEvent; // Work to do in Execute loop
CreateSuspended: boolean = False; // False = start now, True = use Start
max: Int32 = 1; // Max loops of Execute loop
before: TNotifyEvent = nil;// Called before Execute loop
after: TNotifyEvent = nil; // Called after Execute loop
terminate: boolean = True; // When true free the task on termination
task: integer = 1 // Task ID
);
begin
// The thread will only be started when this constructor ends.
inherited Create (CreateSuspended);
FStopEvent := TEvent.Create (nil, True, False, '');
FError_Mess := '';
FTaskID := task;
FTaskName := '';
Max_Loops := max;
OnBeforeWork := before;
OnWork := work;
OnAfterWork := after;
FreeOnTerminate := terminate;
end; // Create //
destructor TSimple_Task.Destroy;
begin
Stop;
Release;
inherited Destroy;
end; // Destroy //
// Execute calls event handler OnWork in a while loop.
// Before the loop is entered, OnBeforeWork is executed, after: OnAfterWork.
procedure TSimple_Task.Execute;
var ret: DWORD;
begin
try
NameThreadForDebugging (AnsiString (FTaskName));
FLoop := 0;
if Assigned (OnBeforeWork) then OnBeforeWork (Self);
while (not Terminated) and (FLoop <> Max_Loops) do
begin
ret := WaitForSingleObject (FStopEvent.Handle, 0);
if ret = WAIT_OBJECT_0 then
begin
Terminate;
end else
begin
if Assigned (OnWork) then OnWork (Self);
FLoop := FLoop + 1;
end; // if
end; // while
if not Terminated and Assigned (OnAfterWork) then OnAfterWork (Self);
// Intercept and ignore the interruption but keep the message
except
on e: exception do FError_Mess := e.Message;
end; // try..except
end; // Execute //
procedure TSimple_Task.Stop;
begin
Terminate;
FStopEvent.SetEvent;
if not FreeOnTerminate
then WaitFor;
end; // Stop //
procedure TSimple_Task.Release;
begin
FStopEvent.Free;
end; // Release //
procedure TSimple_Task.set_name (value: string);
begin
FTaskName := value;
end; // set_name //
end. // Unit: Parallel_Simple_Task //
Upvotes: 1
Views: 3659
Reputation: 595537
TThread.WaitFor()
waits for the thread handle (the TThread.Handle
property) to become signaled when the underlying thread object terminates at the OS layer. That signal happens when TThread
calls the Win32 API ExitThread()
function on itself after your Execute()
method has exited (and after TThread.DoTerminate()
has been called and exited). What you describe sounds like you are encountering a deadlock that is preventing your Execute()
method from exiting correctly even though you may have signaled FStopEvent
to stop your loop. Given the code you have shown, that means either WaitForMultipleObjects()
is returning an error code you are not looking for, or more likely your OnWork
event handler is not exiting correctly at times so Execute()
can then exit itself.
All you have shown so far are the definitions of your task classes themselves, but you have not shown how they are actually being used in your project. Please show the rest of your task logic and stop making people guess what the problem might be.
The first thing I would suggest is take the call to Stop()
out of your destructor. It does not belong there. NEVER destroy a thread that is still running. Always stop the thread first and wait for it to finish terminating before you then destroy it. TThread
by itself has enough problems of its own being destroyed while running, you don't need to add to that.
Upvotes: 7