Producer-Consumer Queue
ThreadPool.ProducerConsumer is the bounded pool. It is backed by a fixed-capacity circular FIFO queue, so producer threads cannot outrun consumer threads without slowing down or being told the queue is full.
Use it when task production can outpace consumption and you need predictable memory usage and overflow control.
uses
ThreadPool.ProducerConsumer;
Pool := TProducerConsumerThreadPool.Create(4, 1024);The constructor takes the worker count and the queue capacity, defaults (0, 1024): 0 selects ProcessorCount and the queue holds up to 1024 callbacks.
Queuing with and without a deadline#
All four callback forms support plain Queue and timeout-aware TryQueue.
Queue waits while the queue is full, up to a compatibility deadline, then raises EQueueFullException:
Pool.Queue(@DoWork); // may block briefly, then raise if still fullTryQueue never raises for capacity; it returns False when its deadline expires:
if not Pool.TryQueue(@DoWork, 50) then
WriteLn('No queue space became available within 50 ms');Because Queue waits only while the queue is actually full, it does not sleep at low load thresholds. New code should state its deadline explicitly with TryQueue.
Submission timeouts#
Submit, TrySubmit, and SubmitRange observe the same rules:
Submituses the compatibility timeout and raisesEQueueFullExceptionon expiry.TrySubmit(..., TimeoutMS, Task)returnsFalsewithTask = nilon a full queue.SubmitRangeruns from a coordinating thread in one admission pass and waits for queue space for its (small) set of chunks.
WaitForAll#
WaitForAll blocks until every accepted callback has finished:
Pool.WaitForAll;
if Pool.WaitForAll(250) then
WriteLn('All finished within 250 ms');As with the Simple pool, it does not close admission; coordinate producers or use Shutdown first. See Lifecycle & Shutdown.
Errors in the two surfaces#
Handle both independently:
- catch
EQueueFullExceptionaround eachQueuecall; - read
LastError/Errors/OnErrorafter work runs.
See Error Handling.
Knowing how full the queue is#
The pool exposes read-only monitoring:
WriteLn('Queued: ', Pool.QueueCount, '/', Pool.QueueCapacity);
WriteLn('Load: ', Pool.QueueLoadFactor:0:2);QueueCountis the number of callbacks currently in the queue.QueueCapacityis the configured queue size.QueueLoadFactorisQueueCount / QueueCapacityin[0, 1].
These are thread-safe snapshots; a count can change immediately after you read it.
Legacy WorkQueue#
WorkQueue remains public so older v0.x programs compile, but do not mutate it in new code. Direct TryEnqueue/TryDequeue/Clear through the queue bypasses the pool's completion accounting and can break WaitForAll and Shutdown. Use the pool-level metrics and submission methods instead.
Rules for the bounded pool#
- Prefer
TryQueue/TrySubmitwith an explicit deadline overQueue. - Never call
SubmitRangefrom one of the pool's own workers (EThreadPoolDeadlock); always coordinate from another thread. - Avoid submitting work from a worker callback to the same bounded pool: a full queue can block the worker's own submission indefinitely while every worker is blocked trying to submit. The Simple pool's queue never blocks, so this hazard does not apply there.
- A cancelled task leaves a tombstone that may occupy its queue slot until it reaches a worker.