I need to have an async consumer method to consume messages from RabbitMq. My problem is that the rabbitmq client for .net rely on an event handler.
I tried to implement a blocking system with a Semaphore, which is working with a low volume. When I'm getting more volume, some messages are lost.
Here is my implementation :
private long _lock;
private string _message;
private object _tag;
private readonly SemaphoreSlim _signal;
public void Configure()
{
Interlocked.Exchange(ref _lock, 0);
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += (sender, ea) =>
{
_message = Encoding.UTF8.GetString(ea.Body.ToArray());
_tag = ea.DeliveryTag;
Interlocked.Exchange(ref _lock, 1);
_signal.Release();
};
_channel.BasicConsume(queue: _rabbitConfig.Queue, autoAck: true, consumer: consumer);
}
public async Task<string> Consume(CancellationToken cancellationToken)
{
while (0 == Interlocked.Read(ref _lock))
{
await _signal.WaitAsync();
}
Interlocked.Exchange(ref _lock, 0);
return _message;
}
I alse tried using BufferBlock but some messages are still lost.
Is there any other way to implement a system keeping my Consume() method ?
Related
We have below class members for timer:
private Timer _activityTimer;
Instantiating this timer variable in one method:
_activityTimer =
new Timer(async (timerState) => await UpdateActivityAsync(_ipAddress), null, new Random().Next(1, 15000), 15000);
But this did not calling periodically when server has load.
It is showing below log in serilog:
Starting HttpMessageHandler cleanup cycle with {InitialCount} items
Ending HttpMessageHandler cleanup cycle after {ElapsedMilliseconds}ms - processed: {DisposedCount} items - remaining: {RemainingItems} items
To handle async periodic callback, I would use System.Threading.PeriodicTimer. This way the execution of the next UpdateActivityAsync will not begin until the last one is done. If you still face thread pool starvation issue, you could manually create an additional thread in which you run the timer.
class Example {
private PeriodicTimer _activityTimer;
private IPAddress _ipAddress = IPAddress.Parse("192.168.1.1");
public void StartTimer() {
// Starting the timer, but not awaiting to not block the calling thread
// If you still face thread pool starvation issue, you could manually create an additional thread here
StartTimerLoopAsync();
}
public void StopTimer() {
_activityTimer.Dispose();
}
private async Task StartTimerLoopAsync() {
_activityTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(15000));
while (await _activityTimer.WaitForNextTickAsync()) {
await UpdateActivityAsync(_ipAddress);
}
}
private async Task UpdateActivityAsync(IPAddress ipAddress) {
await Task.Delay(500); // Simulate some IO
Console.WriteLine(ipAddress);
}
}
UPDATE for .NET Core 3.1
Instead of PeriodicTimer you could simply use Task.Delay (it's not very accurate, but good enough for your use case I believe):
class Example {
private CancellationTokenSource _cancellationTokenSource = new ();
private IPAddress _ipAddress = IPAddress.Parse("192.168.1.1");
public void StartTimer() {
// Starting the timer, but not awaiting to not block the calling thread
// If you still face thread pool starvation issue, you could manually create an additional thread here
StartTimerLoopAsync();
}
public void StopTimer() {
_cancellationTokenSource.Cancel();
}
private async Task StartTimerLoopAsync() {
await Task.Delay(new Random().Next(1, 15000)); // System.Threading.Timer first delay
while (!_cancellationTokenSource.IsCancellationRequested) {
// run the Delay and UpdateActivityAsync simultaneously and wait for both
var delayTask = Task.Delay(15000, _cancellationTokenSource.Token);
await UpdateActivityAsync(_ipAddress, _cancellationTokenSource.Token);
await delayTask;
}
}
private async Task UpdateActivityAsync(IPAddress ipAddress, CancellationToken cancellationToken) {
await Task.Delay(1500, cancellationToken); // Simulate some IO
Console.WriteLine(ipAddress);
}
}
UPDATE
You could also create your own async Timer:
class Example : IDisposable {
private AsyncTimer<Example>? _timer;
public IPAddress IpAddress = IPAddress.Parse("192.168.1.1");
public void StartTimer() {
if (_timer is not null) {
return;
}
_timer = new AsyncTimer<Example>(async (state, ct) => await UpdateActivityAsync(state.IpAddress, ct), this, new Random().Next(1, 15000), 15000);
}
public void StopTimer() {
_timer?.Stop();
}
private async Task UpdateActivityAsync(IPAddress ipAddress, CancellationToken cancellationToken) {
await Task.Delay(500, cancellationToken); // Simulate some IO
Console.WriteLine(ipAddress);
}
public void Dispose() {
_timer?.Dispose();
}
}
class AsyncTimer<T> : IDisposable {
public delegate Task AsyncTimerDelegate(T state, CancellationToken cancellationToken);
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly AsyncTimerDelegate _timerCallback;
private readonly TimeSpan _dueTime;
private readonly TimeSpan _interval;
private readonly T _state;
public AsyncTimer(AsyncTimerDelegate timerCallback, T state, int dueTime, int interval) {
_timerCallback = timerCallback;
_state = state;
_dueTime = TimeSpan.FromMilliseconds(dueTime);
_interval = TimeSpan.FromMilliseconds(interval);
// Starting the timer, but not awaiting to not block the calling thread
// If you still face thread pool starvation issue, you could manually create an additional thread here
StartTimerLoopAsync();
}
public void Stop() {
_cancellationTokenSource.Cancel();
}
private async Task StartTimerLoopAsync() {
await Task.Delay(_dueTime);
while (!_cancellationTokenSource.IsCancellationRequested) {
// run the Delay and UpdateActivityAsync simultaneously and wait for both
var delayTask = Task.Delay(_interval, _cancellationTokenSource.Token);
await _timerCallback.Invoke(_state, _cancellationTokenSource.Token);
await delayTask;
}
}
public void Dispose() {
_cancellationTokenSource.Dispose();
}
}
In an ASP Net Core 2 MVC app, I am using this BackGroundService (via IHostingService) with the below implementation to update singleton gauge objects for a real-time dashboard. However, I don't know of a good way to ensure I capture any exceptions thrown when the Update event is fired.
Note: I am aware of AppDomain.UnhandledException but find it to be more of a sledgehammer approach and would like something easier to maintain and scale.
Or, is there an entirely better way to periodically update data in a background task in ASP.NET Core 2?
public class GaugeUpdater : BackgroundService
{
private readonly List<IUpdateable> _updatables;
private Timer _timer;
public GaugeUpdater (IEnumerable<IUpdateable> updateables)
{
_updatables = updateables.ToList();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!stoppingToken.IsCancellationRequested)
{
await InitializeUpdateables();
SetTimer();
}
}
private void SetTimer()
{
const int intervalMilliseconds = 60_000;
var interval = new TimeSpan(0, 0, 0, 0, intervalMilliseconds);
_timer = new Timer(UpdateAll, null, interval, interval);
}
private async Task InitializeUpdateables()
{
var tasks = _updatables.Select(x => x.Initialize()).ToList();
await Task.WhenAll(tasks);
}
private async void UpdateAll(object state)
{
// TODO: Find way to handle exceptions, as awaiting async void makes it impossible for caller to catch.
// AppDomain.UnhandledException is possible but hard to maintain and handle in this scope.
_updatables.ForEach(async x => await x.Update());
}
}
async void can work for event handlers. Create and event and raise it with the timer. From there you should be able to await async tasks and handle exceptions
public class GaugeUpdater : BackgroundService {
private readonly List<IUpdateable> _updatables;
private Timer _timer;
public GaugeUpdater (IEnumerable<IUpdateable> updateables) {
_updatables = updateables.ToList();
Updating += OnUpdating; //Subscribe to event
}
private event EventHandler Updating = delegate { };
private async void OnUpdating(object sender, EventArgs args) {
try {
var tasks = _updatables.Select(x => x.Update());
await Task.WhenAll(tasks);
} catch {
//TODO: Logging???
}
}
private void UpdateAll(object state) {
Updating(this, EventArgs.Empty); //Raise event
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
if (!stoppingToken.IsCancellationRequested) {
await InitializeUpdateables();
SetTimer();
}
}
private void SetTimer() {
const int intervalMilliseconds = 60_000;
var interval = new TimeSpan(0, 0, 0, 0, intervalMilliseconds);
_timer = new Timer(UpdateAll, null, interval, interval);
}
private async Task InitializeUpdateables() {
var tasks = _updatables.Select(x => x.Initialize()).ToList();
await Task.WhenAll(tasks);
}
}
I have an azure worker role with an event processor host connected to an azure event hub. For some unknown reason - it will not get any messages.
logs show that it opens an EventProcessor for every partition - and there are no errors - but ProcessEventsAsync is never called.
using Service Bus Explorer I can see that it receives messages when the processor is down and when it's on it throws an Exception that a receiver is on.
I did get it to work once, but after a restart it didn't continue working
I have no idea where to look next - this is however the code for the worker role
public class WorkerRole : RoleEntryPoint
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent _runCompleteEvent = new ManualResetEvent(false);
private EventProcessorHost _eventProcessorHost;
private IEventProcessorFactory _processorFactory;
private ConfigurationProvider configuration = new ConfigurationProvider();
private string _eventHubConnectionString;
private string _storageAccountConnectionString;
private string _dbConnectionString;
public override void Run()
{
Trace.TraceInformation("EventHubWorker is running");
try
{
RunAsync(_cancellationTokenSource.Token).Wait();
}
finally
{
_runCompleteEvent.Set();
}
}
public override bool OnStart()
{
Trace.TraceInformation("EventHubWorker is starting");
CompositeResolver.RegisterAndSetAsDefault(FormattersResolver.Instance, ContractlessStandardResolver.Instance, StandardResolver.Instance);
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
SqlMapper.AddTypeHandler(new DateTimeHandler());
_eventHubConnectionString = configuration.EventHubConnectionString;
_dbConnectionString = configuration.DbConnectionString;
_storageAccountConnectionString = configuration.StorageConnectionString;
string hostName = Guid.NewGuid().ToString();
var eventClient = EventHubClient.CreateFromConnectionString(_eventHubConnectionString, configuration.EventHubName);
_eventProcessorHost = new EventProcessorHost(hostName, eventClient.Path, configuration.ConsumerGroupName,
_eventHubConnectionString, _storageAccountConnectionString);
var partitionOptions = new PartitionManagerOptions()
{
LeaseInterval = new TimeSpan(0, 5, 0)
};
_processorFactory = new EventProcessorFactory(/* some data for dependency injection */);
return base.OnStart();
}
public override void OnStop()
{
Trace.TraceInformation("EventHubWorker is stopping");
_cancellationTokenSource.Cancel();
_runCompleteEvent.WaitOne();
base.OnStop();
Trace.TraceInformation("EventHubWorker has stopped");
}
private async Task RunAsync(CancellationToken cancellationToken)
{
int retryCount = 0;
var exceptions = new List<Exception>();
async Task StartProcessing()
{
if (retryCount > 5)
{
throw new AggregateException($"failed to run service, tried {retryCount} times",exceptions);
}
try
{
await _eventProcessorHost.RegisterEventProcessorFactoryAsync(_processorFactory, new EventProcessorOptions
{
InitialOffsetProvider = o => DateTime.UtcNow,
MaxBatchSize = 100,
PrefetchCount = 10,
ReceiveTimeOut = TimeSpan.FromSeconds(20),
});
}
catch(MessagingException e) when (e.IsTransient)
{
retryCount++;
exceptions.Add(e);
await StartProcessing();
}
}
var options = new EventProcessorOptions();
options.ExceptionReceived += Options_ExceptionReceived;
await StartProcessing();
cancellationToken.WaitHandle.WaitOne();
await _eventProcessorHost.UnregisterEventProcessorAsync();
}
private void Options_ExceptionReceived(object sender, ExceptionReceivedEventArgs e)
{
Trace.TraceError(e.Exception.Message);
}
}
This is the EventProcessor Code - The factory itself seems irrelevant
class EventProcessor : IEventProcessor
{
public async Task CloseAsync(PartitionContext context, CloseReason reason)
{
//never logged
Trace.TraceInformation($"Partition {context.Lease.PartitionId} Closed");
if (reason == CloseReason.Shutdown)
{
await context.CheckpointAsync();
}
else
{
Trace.TraceError(reason.ToString());
}
}
public Task OpenAsync(PartitionContext context)
{
//always logs with the expected lease information
Trace.TraceInformation($"Partition {context.Lease.PartitionId} initiailized with epoch {context.Lease.Epoch}");
return Task.FromResult<object>(null);
}
public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
Trace.TraceInformation("processing event"); //never called
// processing code
}
PartitionManagerOptions has a maximum lease interval of 60 seconds, (same as blob lease)
EventProcessorHost won't throw exceptions when initially acquiring leases. Try setting lease interval to 60 seconds instead of 5 minutes.
I would like to create a task to run serial commands on. At this time I do not need to return anything from the method that is doing the work. This will probably change later, but I am now curious as to how this.
This is what I have. I would like to use a separate method for the task instead of creating an anonymous action. I have tried returning void, with the result of "void can not be explicitly converted to a Task". I have also tried. Task<void>. The Last thing I have tried is returning a Task, but I receive, error "Not all Code paths return a value" and "Can not implicily convert void to type task"
In the pass I have used a Thread to accomplish this, but I'd like to use Tasks this time around.
internal class Hardware
{
private EventHandler<SequenceDoneEventArgs> SequenceDone;
private List<Step> Steps;
private System.IO.Ports.SerialPort comport = null;
private Task SequenceTask;
private CancellationTokenSource RequestStopSource;
private CancellationToken RequestStopToken;
private void Initialize()
{
comport = new System.IO.Ports.SerialPort("COM2", 115200, System.IO.Ports.Parity.None,8);
comport.DataReceived += Comport_DataReceived;
}
public async void RunSequence()
{
if (comport == null)
{
Initialize();
}
if (!comport.IsOpen)
{
comport.Open();
}
RequestStopSource = new CancellationTokenSource();
RequestStopToken = RequestStopSource.Token;
SequenceTask = await Task.Run(() => { doSequence(); });
}
private Task doSequence()
{
//** Run Sequence stuff here
}
}
ETA:
In the end this is my the complete solution
internal class Hardware
{
private EventHandler<SequenceDoneEventArgs> SequenceDone;
private List<Step> Steps;
private System.IO.Ports.SerialPort comport = null;
private Task SequenceTask;
private CancellationTokenSource RequestStopSource;
private CancellationToken RequestStopToken;
private void Initialize()
{
comport = new System.IO.Ports.SerialPort("COM2", 115200, System.IO.Ports.Parity.None,8);
comport.DataReceived += Comport_DataReceived;
}
public async void RunSequence()
{
if (comport == null)
{
Initialize();
}
if (!comport.IsOpen)
{
comport.Open();
}
RequestStopSource = new CancellationTokenSource();
RequestStopToken = RequestStopSource.Token;
SequenceTask = await Task.Factory.StartNew(async () => { await doSequence(); });
}
private Task doSequence()
{
//** Run Sequence stuff here
//return null;
return Task.CompletedTask;
}
}
Just mark doSequence as async (assuming it uses await):
private async Task doSequence()
Also, it's a good idea to return this Task in the delegate you pass to Task.Run:
SequenceTask = await Task.Run(() => doSequence());
I would like to create a task to run serial commands on.
This leads me to believe that using async and Task may not be the best solution for your scenario. I suggest you look into TPL Dataflow.
SequenceTask = await Task.Factory.StartNew(async() => { await doSequence(); });
Also your RunSequence() should return Task instead of void.
Actually if you await the Task this should result in the same:
SequenceTask = await doSequence();
I have a class that uses a blocking collection:
public class MessageQueue : IDisposable
{
private BlockingCollection<Message> queue;
private MessageHandler messageHandler;
private CancellationToken cancellationToken;
public MessageQueue(MessageHandler handler, CancellationToken cancellationToken)
{
this.messageHandler = handler;
this.cancellationToken = (cancellationToken != null) ? cancellationToken : new CancellationToken();
for (int i = 0; i < Environment.ProcessorCount; i++)
{
Task.Factory.StartNew(() => DequeueMessage());
}
}
public void Enqueue(Message message)
{
queue.Add(message);
}
private void DequeueMessage()
{
Message message;
while (!cancellationToken.IsCancellationRequested)
{
if (queue.TryTake(out message, 5000, cancellationToken))
{
messageHandler.Handle(message);
}
}
}
}
All looks good as far as all consumer threads correctly start dequeuing messages, messages are enqueued, and all threads take their first message off the queue and start processing. However, only one thread continues processing messages after that. All other threads seem to go off to the thread pool to do other stuff. And I have a log in the dequeue method that would log a thread exiting that method--none of the threads log that. I was not expecting this and I'm baffled. Is there a good explanation for this behavior that I'm not understanding?