From 5fbbfd75903089af8d37cca463d5793451d7f4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Mon, 20 Oct 2025 12:15:31 +0200 Subject: [PATCH] Keep TaskThreadPool workers idle until work arrives --- PerfectNumbers.Core/TaskThreadPool.cs | 57 ++++++++++++++++----------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/PerfectNumbers.Core/TaskThreadPool.cs b/PerfectNumbers.Core/TaskThreadPool.cs index 80a106a..81fdee8 100644 --- a/PerfectNumbers.Core/TaskThreadPool.cs +++ b/PerfectNumbers.Core/TaskThreadPool.cs @@ -18,15 +18,21 @@ internal sealed class TaskThreadPool : IDisposable public Task Task { get; } } - private readonly ConcurrentQueue _pendingTasks = new(); - private readonly SemaphoreSlim _availableTasks = new(0); + private readonly ConcurrentQueue _pendingTaskQueue = new(); + private readonly BlockingCollection _pendingTasks; private readonly Thread[] _threads; private readonly Action _taskExecutor; private int _disposed; public TaskThreadPool(int minimumThreads, Action taskExecutor) { + if (minimumThreads <= 0) + { + throw new ArgumentOutOfRangeException(nameof(minimumThreads)); + } + _taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor)); + _pendingTasks = new BlockingCollection(_pendingTaskQueue); _threads = new Thread[minimumThreads]; for (int i = 0; i < minimumThreads; i++) @@ -46,18 +52,33 @@ internal sealed class TaskThreadPool : IDisposable public void Queue(Task task) { + if (task is null) + { + throw new ArgumentNullException(nameof(task)); + } + if (Volatile.Read(ref _disposed) != 0) { throw new ObjectDisposedException(nameof(TaskThreadPool)); } - _pendingTasks.Enqueue(new WorkItem(task)); - _availableTasks.Release(); + try + { + _pendingTasks.Add(new WorkItem(task)); + } + catch (InvalidOperationException ex) when (Volatile.Read(ref _disposed) != 0) + { + throw new ObjectDisposedException(nameof(TaskThreadPool), ex); + } + catch (ObjectDisposedException ex) + { + throw new ObjectDisposedException(nameof(TaskThreadPool), ex); + } } public IEnumerable GetScheduledTasks() { - WorkItem[] snapshot = _pendingTasks.ToArray(); + WorkItem[] snapshot = _pendingTaskQueue.ToArray(); if (snapshot.Length == 0) { @@ -81,36 +102,28 @@ internal sealed class TaskThreadPool : IDisposable return; } - for (int i = 0; i < _threads.Length; i++) - { - _availableTasks.Release(); - } + _pendingTasks.CompleteAdding(); foreach (Thread thread in _threads) { thread.Join(); } - _availableTasks.Dispose(); + _pendingTasks.Dispose(); } private void WorkerLoop() { - while (true) + try { - _availableTasks.Wait(); - - if (Volatile.Read(ref _disposed) != 0) + foreach (WorkItem workItem in _pendingTasks.GetConsumingEnumerable()) { - return; + _taskExecutor(workItem.Task); } - - if (!_pendingTasks.TryDequeue(out WorkItem workItem)) - { - continue; - } - - _taskExecutor(workItem.Task); + } + catch (ObjectDisposedException) + { + // The collection was disposed while the worker was waiting. Exit gracefully. } } }