From eda3c2128af972bf7fe74c020cd4e836fdf0ab8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 19 Oct 2025 21:51:22 +0200 Subject: [PATCH] Align task scheduler defaults with guidance --- PerfectNumbers.Core/TaskThreadPool.cs | 116 ++++++++++++++++++ PerfectNumbers.Core/UnboundedTaskScheduler.cs | 46 ++++--- 2 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 PerfectNumbers.Core/TaskThreadPool.cs diff --git a/PerfectNumbers.Core/TaskThreadPool.cs b/PerfectNumbers.Core/TaskThreadPool.cs new file mode 100644 index 0000000..80a106a --- /dev/null +++ b/PerfectNumbers.Core/TaskThreadPool.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace PerfectNumbers.Core; + +internal sealed class TaskThreadPool : IDisposable +{ + private readonly struct WorkItem + { + public WorkItem(Task task) + { + Task = task; + } + + public Task Task { get; } + } + + private readonly ConcurrentQueue _pendingTasks = new(); + private readonly SemaphoreSlim _availableTasks = new(0); + private readonly Thread[] _threads; + private readonly Action _taskExecutor; + private int _disposed; + + public TaskThreadPool(int minimumThreads, Action taskExecutor) + { + _taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor)); + _threads = new Thread[minimumThreads]; + + for (int i = 0; i < minimumThreads; i++) + { + Thread thread = new(WorkerLoop) + { + IsBackground = true, + Name = $"{nameof(TaskThreadPool)}-{i + 1}" + }; + + _threads[i] = thread; + thread.Start(); + } + } + + public int ThreadCount => _threads.Length; + + public void Queue(Task task) + { + if (Volatile.Read(ref _disposed) != 0) + { + throw new ObjectDisposedException(nameof(TaskThreadPool)); + } + + _pendingTasks.Enqueue(new WorkItem(task)); + _availableTasks.Release(); + } + + public IEnumerable GetScheduledTasks() + { + WorkItem[] snapshot = _pendingTasks.ToArray(); + + if (snapshot.Length == 0) + { + return Array.Empty(); + } + + Task[] tasks = new Task[snapshot.Length]; + + for (int i = 0; i < snapshot.Length; i++) + { + tasks[i] = snapshot[i].Task; + } + + return tasks; + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) != 0) + { + return; + } + + for (int i = 0; i < _threads.Length; i++) + { + _availableTasks.Release(); + } + + foreach (Thread thread in _threads) + { + thread.Join(); + } + + _availableTasks.Dispose(); + } + + private void WorkerLoop() + { + while (true) + { + _availableTasks.Wait(); + + if (Volatile.Read(ref _disposed) != 0) + { + return; + } + + if (!_pendingTasks.TryDequeue(out WorkItem workItem)) + { + continue; + } + + _taskExecutor(workItem.Task); + } + } +} diff --git a/PerfectNumbers.Core/UnboundedTaskScheduler.cs b/PerfectNumbers.Core/UnboundedTaskScheduler.cs index 56e6f49..ec9209f 100644 --- a/PerfectNumbers.Core/UnboundedTaskScheduler.cs +++ b/PerfectNumbers.Core/UnboundedTaskScheduler.cs @@ -1,27 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + namespace PerfectNumbers.Core; public sealed class UnboundedTaskScheduler : TaskScheduler { - public static UnboundedTaskScheduler Instance { get; } = new(); + public static UnboundedTaskScheduler Instance { get; } = new(); - private UnboundedTaskScheduler() - { - ThreadPool.SetMaxThreads(100_000, 100_000); - ThreadPool.SetMinThreads(10_340, 100); - } + private readonly TaskThreadPool _threadPool; - public override int MaximumConcurrencyLevel => int.MaxValue; + private UnboundedTaskScheduler() + { + _threadPool = new TaskThreadPool(10_340, ExecuteTask); + } - protected override void QueueTask(Task task) - { - ThreadPool.UnsafeQueueUserWorkItem( (state) => - { - var (scheduler, task) = ((UnboundedTaskScheduler, Task))state!; - scheduler.TryExecuteTask(task); - }, (this, task)); - } + public override int MaximumConcurrencyLevel => int.MaxValue; - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => TryExecuteTask(task); + protected override void QueueTask(Task task) + { + _threadPool.Queue(task); + } - protected override IEnumerable? GetScheduledTasks() => null; + private void ExecuteTask(Task task) + { + TryExecuteTask(task); + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + return TryExecuteTask(task); + } + + protected override IEnumerable? GetScheduledTasks() + { + return _threadPool.GetScheduledTasks(); + } }