c00000005 ACCESS_VIOLATION caused when closing the Form (exiting application) while the following thread is running (this is just an EXAMPLE, to illustrate the problem):
type
Twtf = class(TThread)
protected
procedure Execute; override;
end;
procedure Twtf.Execute;
const
hopefully_big_enough_to_trigger_problem = 100000000;
var
A: array of Integer;
I: Integer;
begin
SetLength(A, hopefully_big_enough_to_trigger_problem);
while not Terminated do begin
for I := Low(A) to High(A) do
A[I] := 0;
end;
end;
var
wtf: Twtf;
procedure TForm1.Button4Click(Sender: TObject);
begin
wtf := Twtf.Create;
wtf.FreeOnTerminate := True;
end;
What is happening according to the Local Variables debug window is that A
is now ()
, namely Length(A) is 0. Obviously this is why accessing A[I]
causes problems, but note that I did not manually do anything to A
.
How do I stop this error from showing (does not necessarily have to be prevented from happening)? The application is closing, the thread should just die... QUIETLY. Which actually happens for smaller arrays, but with a sufficiently large one 1 or 2 out of every 3 attempts results in the problem (if run with debugging it is shown always; i.e., this always happens, but is sometimes simply hidden).
The following does not help:
destructor TForm1.Destroy;
begin
wtf.terminate;
inherited;
end;
Only doing TerminateThread(wtf.Handle, 0);
in the destructor instead solves (hides) the problem. But surely there is a more graceful way?
To reiterate:
This was an example crafted to illustrate what happens when a thread loop is accessing a dynamic array when the application is terminated (regardless of whatever graceful shutdown mechanisms, the thread may be within the loop at that point in time, and blocking the application while waiting a possibly long while for it to finish or get to a if Terminated
check is not optimal for obvious reasons).
The problem is caused by the application freeing the array's memory before it frees the thread, WHILE the thread is working on the array (the array is a variable belonging to the thread); e.g., with a sufficiently large multidimensional A: array of array of Integer
, it is actually possible to see A[500000]
freed to ()
while A[400000]
is still fine.
The solution is so simple as to be trivial:
Twtf = class(TThread)
...
var
wtf: Twtf;
...
destructor TForm1.Destroy;
begin
// Only Form in the Application, otherwise add Application.Terminated check?
if wtf <> nil then
with wtf do
if not Finished then
Suspend;
inherited;
end;
This eliminates the race condition between the thread working on its array and the application freeing that array.
Note that the thread will NOT call its destructor, so resources (if necessary) should be freed manually.
All of the above is irrespective of FreeOnTerminate
.
EDIT:
Added Finished
check
EDIT:
Alternative to deprecated Suspend: SuspendThread(Handle);