Consuming an AMQ queue in a BackgroundService - c#

I have an ASP.Net Core Web API application which consumes messages from an AMQ Queue. Currently I have the consuming code in a BackgroundService with an event handler hooked up to the Listener. The whole thing is in a while look (checking the cancellation token) to ensure any errors are handled and we retry the subscription but I also have an inner while loop to keep the service alive but it doesn't need to do anything.
My question is, what should I do inside that inner while loop to make sure I don't consume unnecessary CPU; e.g. Task.Yield(), Task.Delay(something)?
public class ReceiverService : BackgroundService
{
...
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
...
while (!stoppingToken.IsCancellationRequested)
{
...
IConnectionFactory factory =
new NMSConnectionFactory(
$"activemq:ssl://{parsed?["message"]}:51513?wireFormat.maxInactivityDuration=0");
connection = await factory.CreateConnectionAsync(Username, Password);
var session = await connection.CreateSessionAsync();
var destination = await session.GetQueueAsync("queuename/" + subscriptionId);
var consumer = await session.CreateConsumerAsync(destination);
consumer.Listener += async message =>
{
// do stuff with message
message.Acknowledge();
};
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(0, stoppingToken);
}
await connection?.CloseAsync()!;
await Task.Delay(1000, stoppingToken);
}
}
}
Cheers
Rich

If you have nothing but cleanup to do, then you can just do await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);. No need for any loops at all.

Related

A ReactiveX observable like ReplaySubject but replaying only items that have never been observed?

I need an Rx.NET observable that accumulates items while there is no active subscribers, and emits the whole accumulated sequence (and any future items) to new subscribes, as soon as there is any.
It is different from ReplaySubject in that it doesn't keep the items which have been once replayed to any subscriber. Thus, once a queued item has been observed by the current subscribers, it gets removed from the queue and won't be seen by any new future subscribers.
Can something like that be composed using standard Rx.NET operators?
I need it to tackle races conditions in the following scenario. There is a looping async workflow RunWorkflowAsync which needs to perform ResetAsync task when it observes a specific ResetRequestedEvent message.
Here's the whole thing as a .NET 6 console app:
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Channels;
try
{
await TestAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
async Task TestAsync()
{
var resetRequestsSubject = new Subject<ResetRequestedEvent>();
using var cts = new CancellationTokenSource(20000);
await Task.WhenAll(
SimulateResetRequests(cts.Token),
RunWorkflowAsync(resetRequestsSubject, cts.Token));
// simulate emmiting reset requests
async Task SimulateResetRequests(CancellationToken cancelToken)
{
async Task Raise(int n, int delay)
{
var ev = new ResetRequestedEvent(n);
Console.WriteLine($"{ev} issued");
resetRequestsSubject!.OnNext(ev);
await Task.Delay(delay, cancelToken);
}
await Raise(1, 50);
await Raise(2, 50);
await Raise(3, 50);
await Raise(4, 1000);
await Raise(5, 5000);
await Raise(6, 4000);
await Raise(7, 3000);
resetRequestsSubject.OnCompleted();
}
// simulate the reset task
async Task ResetAsync(CancellationToken cancelToken)
{
await Task.Delay(1000, cancelToken);
Console.WriteLine("Reset done");
}
// simulate the work task
async Task DoWorkAsync(CancellationToken cancelToken)
{
await Task.Delay(2000, cancelToken);
Console.WriteLine("Work done");
}
// do reset, then work in a loop until cancelled
async Task RunWorkflowAsync(IObservable<ResetRequestedEvent> resetRequests, CancellationToken externalCancelToken)
{
// from this point, make sure reset requests don't go unobserved
var accumulatedResetRequests = resetRequests.Accumulate(externalCancelToken);
using var auto1 = accumulatedResetRequests.Connect();
while (true)
{
externalCancelToken.ThrowIfCancellationRequested(); // stops the whole workflow
using var internalCts = CancellationTokenSource.CreateLinkedTokenSource(externalCancelToken);
var internalCancelToken = internalCts.Token;
// signal cancellation upon the most recent reset request
using var auto2 = accumulatedResetRequests
.Do(ev => Console.WriteLine($"{ev} seen"))
.Throttle(TimeSpan.FromMilliseconds(100))
.Do(ev => Console.WriteLine($"{ev} acted upon"))
.Subscribe(_ => internalCts.Cancel());
try
{
// start with a reset
await ResetAsync(internalCancelToken);
// do work until another reset is requested
while (true)
{
await DoWorkAsync(internalCancelToken);
}
}
catch (OperationCanceledException)
{
}
}
}
}
record ResetRequestedEvent(int Number);
public static class RxExt
{
class CumulativeObservable<T> : IConnectableObservable<T>
{
readonly IObservable<T> _source;
readonly Channel<T> _channel;
readonly CancellationToken _cancelToken;
public CumulativeObservable(IObservable<T> source, CancellationToken cancellationToken)
{
_source = source;
_channel = Channel.CreateUnbounded<T>();
_cancelToken = cancellationToken;
}
public IDisposable Connect() =>
_source.Subscribe(
onNext: item => _channel.Writer.TryWrite(item),
onError: ex => _channel.Writer.Complete(ex),
onCompleted: () => _channel.Writer.Complete());
public IDisposable Subscribe(IObserver<T> observer) =>
_channel.Reader.ReadAllAsync(_cancelToken).ToObservable().Subscribe(observer);
}
public static IConnectableObservable<T> Accumulate<T>(
this IObservable<T> #this,
CancellationToken cancellationToken) =>
new CumulativeObservable<T>(#this, cancellationToken);
}
The idea is to stop all pending tasks inside RunWorkflowAsync and perform ResetAsync when ResetRequestedEvent message comes along.
I realize there's more than one way to cook an egg (and implement RunWorkflowAsync), but I like this approach as I don't need to think about thread safety when I use and recycle internalCts cancellation token source (to stop all pending task before another iteration).
Above, CumulativeObservable does what I want, but it's a very naive implementation which only supports one concurrent observable (unlike, say, ReplaySubject) and lacks any safety checks.
I'd prefer a composition that can be built using standard operators.

Await inside System.Threading.Timer callback

I am doing an ASP.NET Web API and have a BackgroundService like this:
Inside Doing, I to await a task 1:
The problem is with the TimeSpan.FromSeconds(0.5)) the ExecuteAsync will do create a new Doing() without waiting for my task to be done.
The console result :
How can I resolve this? Or is there a way to achieve a background task with await for the task completion?
Don't use a Timer.
Instead set up a loop and use Task.Delay for the wait period.
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var delay = TimeSpan.FromSeconds(0.5);
while (!cancellationToken.IsCancellationRequested)
{
await Doing();
await Task.Delay(delay, cancellationToken);
}
}
See an example in Microsofts documentation.

Reacting on cancellation in BackgroundService from outside

If I start the following example in .NET Core BackgroundService on debug mode:
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Task.Run(async () => await Task.Delay(30000, stoppingToken))
.Wait(stoppingToken);
}
}
the Ctrl + C cancellation event does not call the StopAsync() Method which is calling the Cancel() from the CancellationTokenSource.
I think my problem is similar to this post.
How can I catch those cancellations when I'm using blocking methods inside the ExecuteAsync?
p.s.: In the real world my ExecuteAsync is watching the filesystem until a new file is created in my destination. To achieve this behavior I'm using the FileSystemWatcher.WaitForChanged() method.
From the comments, it looks like the problem has little to do with threads. The real problem is how to stop a FileSystemWatcher.
You don't need an extra thread with a FileSystemWatcher, you need to handle its change events as quickly as possible. You can use an asynchronous event handler for this, or even better, quickly post events to a queue or Channel for processing.
To stop the FSW you can use the CancellationToken.Register method to set EnableRaisingEvents to false :
stoppingToken.Register(()=>watcher.EnableRaisingEvents=false);
Event processing
To quickly handle events, one could post the FileSystemEventArgs values directly to a queue or a Channel and process them with another tasks. This has two benefits:
File events are handled as fast as possible, so none is lost
The code can either wait for all events to finish, or cancel them.
var channel=Channel.CreateUnbounded<FileSystemEventArgs>();
stoppingToken.Register(()=>{
watcher.EnableRaisingEvents=false;
channel.Writer.TryComplete();
});
watcher.Changed+=(o,e)=>channel.Writer.WriteAsync(e,stoppingToken);
await foreach(var e in channel.Reader.ReadAllAsync(stoppingToken))
{
//do something
}
A Channel can be treated as a queue with asynchronous read and write operation. The ReadAllAsync method dequeues messages until stopped and returns them as an IAsyncEnumerable which allows the use of await foreach to easily handle items asynchronously.
Pipelines and Channels
The code can be refactored into this:
await watcher.AsChannel(stoppingToken)
.ProcessEvents(stoppingToken);
The consumer
It's easy to extract the subscriber code into a separate method. This could even be an extension method:
public static async Task ProcessEvents(this ChannelReader<FileSystemEventArgs> reader,CancellationToken stoppingToken)
{
await foreach(var e in channel.Reader.ReadAllAsync(stoppingToken))
{
//do something
}
}
And call it :
var channel=Channel.CreateUnbounded<FileSystemEventArgs>();
stoppingToken.Register(()=>{
watcher.EnableRaisingEvents=false;
channel.Writer.TryComplete();
});
watcher.Changed+=(o,e)=>channel.Writer.WriteAsync(e,stoppingToken);
await ProcessEvents(channel,stoppingToken);
This works because a Channel has implicit cast operators to ChannelReader and ChannelWriter.
A ChannelReader supports multiple consumers, so one could use multiple tasks to process events, eg :
public static async Task ProcessEvents(this ChannelReader<FileSystemEventArgs> reader,int dop,CancellationToken stoppingToken)
{
var tasks=Enumerable.Range(0,dop).Select(()=>{
await foreach(var e in channel.Reader.ReadAllAsync(stoppingToken))
{
//do something
}
});
await Task.WhenAll(tasks);
}
The producer
It's also possible to extract the channel creation and posting into a separate method. After all, we only need the ChannelReader for processing:
public static ChannelReader AsChannel(this FileSystemWatcher watcher, CancellationToken stoppingToken)
{
var channel=Channel.CreateUnbounded<FileSystemEventArgs>();
stoppingToken.Register(()=>{
watcher.EnableRaisingEvents=false;
channel.Writer.TryComplete();
});
watcher.Changed+=(o,e)=>channel.Writer.WriteAsync(e,stoppingToken);
return channel;
}
And combine everything in a simple pipeline:
await watcher.AsChannel(stoppingToken)
.ProcessEvents(stoppingToken);
My first workaround at the moment is this:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var blockingTask = Task.Run(async () => await Task.Delay(30000, stoppingToken));
await Task.WhenAny(blockingTask);
}
}
#Panagiotis Kanavos I appreciate your efforts, I'm coming back to your detailed post If I'm trying to change my "blocking" FSW to an event driven FSW.
In productive I'm using something like this:
private void DoServiceWork()
{
// Some Work if new PDF or docx file is available
// ...
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
int myTimeout = 1000 * 60 * 60; // 1 hour
while (!stoppingToken.IsCancellationRequested)
{
pdfWatchingTask = Task.Run(() => MyFSWLibrary.Watch(directory, "*.pdf", myTimeout, stoppingToken));
docWatchingTask = Task.Run(() => MyFSWLibrary.Watch(directory, "*.docx", myTimeout, stoppingToken));
var finishedTask = await Task.WhenAny(new Task<MyFSWResult>[] { waitPdfTask, waitXmpTask });
if(finishedTask.Result.Success) DoServiceWork();
}
}

Why does my background worker deadlock when using Task.Delay?

In my project I'm using ASP.NET Core 3.1 with a hosted service background worker.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var result = await _messageBus.Get();
if (result != null)
{
await _dbContext.UpdateData(result, stoppingToken);
}
await Task.Delay(5000, stoppingToken);
}
}
Inside my DbContext I do some logic then call await command.ExecuteNonQueryAsync(stoppingToken);. On that line the worker deadlocks.
await using var connection = new SqlConnection(dbConnection);
await connection.OpenAsync(stoppingToken);
var command = connection.CreateCommand();
command.CommandText = query;
await command.ExecuteNonQueryAsync(stoppingToken);
I then changed the background worker to:
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_timer = new Timer(async state => await Run(state, stoppingToken), null,
TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private async Task Run(object state, CancellationToken stoppingToken)
{
var result = await _messageBus.Get();
if (result != null) await _dbContext.UpdateData(result, stoppingToken);
}
This ended up working and avoided a deadlock. However I honestly don't know why this worked and avoided a deadlock. What makes the the Timer class different than just using Task.Delay?
I strongly suspect that your code is not actually asynchronous, which will cause startup issues (that can look like deadlocks). It's not documented well, but ExecuteAsync must be asynchronous. So if you have blocking code at the beginning of that method (e.g., if the "get message from the message bus" code is actually synchronously blocking until a message is received), then you'll need to wrap it in a Task.Run:
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
var result = await _messageBus.Get();
if (result != null)
{
await _dbContext.UpdateData(result, stoppingToken);
}
await Task.Delay(5000, stoppingToken);
}
});
In my own code, I use a separate base type to do the Task.Run so it's less ugly.

Blocking call in BackgroundService makes yet another BackgroundService not run

I have 2 background services.
Both of them have a call to a method that is NOT async. This method I cannot control - I can wrap it, sure.
I wire the backgroundservice(s) up by adding them to the servicecollection:
services.AddSingleton<IHostedService, BS1>();
services.AddSingleton<IHostedService, BS2>();
The execute async looks like this in each of them.
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
log.LogInformation($"Start consuming from topic: {eventStreamConsumer.Topic}");
while (stoppingToken.IsCancellationRequested == false)
{
try
{
async...
var consumeResult = eventStreamConsumer.Consume();
The Consume method is blocking the thread here since it's not async.
My approach so far has been to wrap the inner workings of execute async into a Task.Factory.Start, but if the Consume does not return, the thread still hangs.
I would like to truly run this on a separate thread, but bear in mind the execute async has dependencies on other instances in the class - don't know if the will be a problem?
How does this approach look like?
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var task = new Task(() =>
{
while (stoppingToken.IsCancellationRequested == false)
{
try
{
var consumeResult = eventStreamConsumer.Consume();
....
}
catch (Exception e)
{
//swallow
}
}
}, stoppingToken, TaskCreationOptions.LongRunning);
task.Start();
return Task.FromResult<object>(null);
}
The way async works is exactly that it returns a Task. That's the part you're missing: instead of waiting for the task to finish or reading its Result, return the task itself.
Also, don't use new Task followed by Task.Start. Task.Run is what you actually want pretty much every time (the only exception being if you're creating your own task scheduler).

Categories