The basic problem lies in the implementation of the RTL library as soon as the ThreadPool destructor collides with the FreeLibrary limitations. TThreadPool.Destroy has a fix to address this issue, note IsDLLDetaching in the code below. Delphi 11 Upd 3
unit System.Threading;
destructor TThreadPool.Destroy;
var
I: Integer;
Thread: TBaseWorkerThread;
LocalFreeList: TList<TBaseWorkerThread>;
begin
FShutdown := True;
{$IFDEF MSWINDOWS}
if IsDLLDetaching then
begin
if (ThreadPoolMonitorHandles <> nil) and ThreadPoolMonitorHandles.ContainsKey(self) then
begin
TerminateThread( ThreadPoolMonitorHandles[self], 0);
ThreadPoolMonitorHandles.Remove(self);
end;
with FThreads.LockList do
try
for I := 0 to Count - 1 do
TerminateThread( Items[I].Handle, 0 );
finally
FThreads.UnLockList;
end;
FWorkerThreadCount := 0;
end
else
{$ENDIF MSWINDOWS}
begin
if FQueue <> nil then
begin
TMonitor.Enter(FQueue);
try
TMonitor.PulseAll(FQueue);
finally
TMonitor.Exit(FQueue);
end;
end;
if FThreads <> nil then
begin
LocalFreeList := TList<TBaseWorkerThread>.Create;
try
with FThreads.LockList do
try
// Check each thread to see if it is already marked for termination and/or if it is hung.
for I := 0 to Count - 1 do
begin
Thread := Items[I];
LocalFreeList.Add(Thread);
end;
finally
FThreads.UnlockList
end;
for I := 0 to LocalFreeList.Count - 1 do
LocalFreeList[I].DisposeOf;
finally
LocalFreeList.Free;
end;
end;
Assert(FWorkerThreadCount = 0);
WaitMonitorThread;
end;
FThreads.Free;
FQueue.Free;
FQueues.Free;
FRetiredThreadWakeEvent.Free;
inherited;
end;
However, doesn't work as expected with the BPL-enabled build. For some reason, the Delphi dev team based the fix on System.IsLibrary flag