// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; namespace EpicGames.Core { /// /// Manages a set of tasks run in the background on a fixed number of tasks. Similar to . /// public sealed class AsyncQueue : IAsyncDisposable { readonly List _workerTasks = []; readonly Channel> _queuedTasks = Channel.CreateUnbounded>(); readonly CancellationTokenSource _cancellationSource = new CancellationTokenSource(); /// /// Constructor /// /// Number of concurrent workers executing tasks public AsyncQueue(int numWorkers) { for (int idx = 0; idx < numWorkers; idx++) { _workerTasks.Add(Task.Run(() => RunWorkerAsync(), _cancellationSource.Token)); } } /// /// Finish executing all the tasks and wait for them to complete /// /// Cancellation token for the operation public async Task StopAsync(CancellationToken cancellationToken) { _queuedTasks.Writer.TryComplete(); await Task.WhenAll(_workerTasks).WaitAsync(cancellationToken); } /// public async ValueTask DisposeAsync() { await _cancellationSource.CancelAsync(); await StopAsync(CancellationToken.None).ConfigureAwait(false); _cancellationSource.Dispose(); } /// /// Enqueue a task to be executed /// /// public void Enqueue(Func task) => _queuedTasks.Writer.TryWrite(task); /// /// Worker executing tasks. Any exception encountered will be bubbled up as all calls are awaited. /// private async Task RunWorkerAsync() { try { await foreach (Func task in _queuedTasks.Reader.ReadAllAsync(_cancellationSource.Token)) { try { await task(_cancellationSource.Token); } catch { } } } catch (OperationCanceledException) { } } } }