Lifecycle & Shutdown
Both pools share one lifecycle contract. The pool state machine moves monotonically from accepting, to draining, to stopped:
tpsAccepting -> tpsDraining -> tpsStoppedState is read-only and observable from any thread.
Waiting#
WaitForAll blocks until every accepted callback has finished.
Pool.WaitForAll; // no deadline
Ready := Pool.WaitForAll(250); // deadline in millisecondsThis is the gate before reading results, checking errors, or freeing callback targets.
Shut down#
Shutdown performs a controlled stop:
- Stops new admission atomically.
- Waits for submissions already passing admission to finish enqueueing.
- Drains every accepted task to completion.
- Sets
Terminatedon each worker, wakes, joins, and frees them. - Publishes
tpsStoppedand wakes concurrent shutdown callers.
After Shutdown, Queue, TryQueue, Submit, and TrySubmit raise EThreadPoolShutdown. The pool is permanently stopped; a later call to Shutdown is safe and a no-op.
Pool.Shutdown; // stop admission, drain, join workers
WriteLn(Pool.State = tpsStopped);Destruction calls Shutdown automatically, so the try/finally Free pattern drains accepted work even if you forget the explicit call.
Rights and restrictions#
Shutdownis idempotent and never blocks a second caller once the first finishes.Shutdownmust not be called from one of the pool's own worker threads; the library raisesEThreadPoolShutdownrather than deadlock the worker.- Waiting for a pool never reduces its own admission:
WaitForAllandShutdownare the only lifecycle operations.
Why not call Free early?#
Destroy = Shutdown + resource release. It joins every worker, so freeing a pool while callbacks are still running is safe from the pool's perspective. The danger is your own objects: free callback targets only after WaitForAll returns, otherwise a still-running method dereferences a freed object.
Worker-thread hazards#
- Calling
Shutdownfrom a callback running on that pool raises. - A callback waiting on its own task handle raises
EThreadPoolDeadlock(see Tasks & Batches). - On the Simple pool, callbacks may queue more work (the queue never blocks). On the bounded pool, a worker submitting to its own full queue can block without a bound; avoid it (see Producer-Consumer).
Putting it together#
Pool := TProducerConsumerThreadPool.Create(4, 1024);
try
// Producers finish while admission is open.
for I := 1 to NumberOfJobs do
Pool.Queue(@DoWork);
// Barrier: nothing is accepted after this call starts passing.
Pool.Shutdown;
finally
Pool.Free; // already stopped; resource release only
end;
// Safe to read shared results here: Shutdown drained everything.Using Shutdown as the final barrier removes the "another producer still submitting" ambiguity.