using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects
{
///
/// Queue for processing items
///
/// Item type
public class ProcessQueue
{
private readonly Channel _channel;
private readonly Func _processor;
private Task? _processTask;
private CancellationTokenSource? _cts;
private bool _processTillEmpty;
///
/// Event for when an exception is thrown in the processing handler
///
public event Action? Exception;
///
/// ctor
///
/// The function to async handle the updates
/// The max number of items to be queued up. When happens when the queue is full and a new write is attempted can be specified with fullMode
/// What should happen if the queue contains maxQueuedItems pending items. If no max is set this setting is ignored
public ProcessQueue(Func processor, int? maxQueuedItems = null, QueueFullBehavior? fullBehavior = null)
{
_processor = processor;
if (maxQueuedItems == null)
{
_channel = Channel.CreateUnbounded(new UnboundedChannelOptions
{
AllowSynchronousContinuations = false,
SingleReader = true,
SingleWriter = true
});
}
else
{
_channel = Channel.CreateBounded(new BoundedChannelOptions(maxQueuedItems.Value)
{
AllowSynchronousContinuations = false,
SingleReader = true,
SingleWriter = true,
FullMode = MapMode(fullBehavior)
});
}
}
private BoundedChannelFullMode MapMode(QueueFullBehavior? behavior) =>
behavior switch
{
QueueFullBehavior.DropOldest => BoundedChannelFullMode.DropOldest,
QueueFullBehavior.DropNewest => BoundedChannelFullMode.DropNewest,
QueueFullBehavior.DropWrite => BoundedChannelFullMode.DropWrite,
_ => BoundedChannelFullMode.DropWrite
};
///
/// Start the processing of the queue
///
public Task StartAsync()
{
_cts = new CancellationTokenSource();
_processTask = Task.Run(async () =>
{
try
{
await foreach (var item in _channel.Reader.ReadAllAsync(_cts.Token).ConfigureAwait(false))
{
if (_cts.IsCancellationRequested && !_processTillEmpty) // Items might still be processed even if CT is canceled
return;
try
{
await _processor.Invoke(item).ConfigureAwait(false);
}
catch (Exception ex)
{
Exception?.Invoke(ex);
}
}
}
catch (OperationCanceledException) { }
});
return Task.CompletedTask;
}
///
/// Stop processing the queue
///
/// Whether updates still pending in the queue should be discarded
///
public async Task StopAsync(bool discardPending = true)
{
if (_processTask == null)
return;
_processTillEmpty = !discardPending;
_cts!.Cancel();
await _processTask.ConfigureAwait(false);
_channel.Writer.TryComplete(_processTask.Exception);
}
///
/// Write an update to queue
///
public bool Write(T item)
{
if (_cts?.IsCancellationRequested == true)
return false;
var write = _channel.Writer.TryWrite(item);
if (!write)
LibraryHelpers.StaticLogger?.Log(LogLevel.Warning, "Failed to write item to process queue. Item will be discarded");
return write;
}
}
}