I've implemented the BackgroundQueue as explained here, and as shown:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Do some work...
});
return Ok();
}
I registered the BackgroundQueue with Autofac as:
builder.RegisterType<BackgroundQueue>()
.As<IBackgroundQueue>()
.SingleInstance();
So far so good. I call my controller action and the task is added to the queue. And there it stays without being executed.
So how do I get the task to execute?
The BackgroundQueue implementation that you took from the documentation is only one part to the solution: The background queue will just keep track of the jobs that you want to be executed.
What you will also need is right below that in the docs: The QueuedHostedService. This is a background service that gets registered with the DI container and is started when the application starts. From then on, it will monitor your BackgroundQueue and work off jobs as they get queued.
A simplified example implementation of this background service, without logging or error handling, could look like this:
public class QueuedHostedService : BackgroundService
{
private readonly IBackgroundQueue _backgroundQueue;
public QueuedHostedService(IBackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _backgroundQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Related
I am building a microservice which is supposed to send periodical notifications through various means.
To process the notifications and triggers I intend to use a BackgroundService which will look into database, call appropriate service based on notification type and mark into the database the notification as being sent.
How can I access the database from the background service in a safe way, not having concurrency issues?
Is it enough to inject IServiceProvider and create a scope?
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyBackgroundService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
using (IServiceScope scope = _serviceProvider.CreateScope())
{
IRepository<Notification> notificationRepository =
scope.ServiceProvider.GetRequiredService<IRepository<Notification>>();
IRepository<NotificationLog> notificationLogRepository =
scope.ServiceProvider.GetRequiredService<IRepository<NotificationLog>>();
IUnitOfWork unitOfWork =
scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
while(true)
{
if(stoppingToken.IsCancellationRequested)
{
return;
}
var list = await notificationRepository.GetAll();
.....................................
await notificationLogRepository.Add(...);
await unitOfWork.Commit();
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await base.StopAsync(stoppingToken);
}
}
Is it enough to inject IServiceProvider and create a scope?
Yes.
While you can create a single scope for your entire background worker's lifetime, you also can create a scope per "invocation" - in this case, you could create a new scope each time the timer goes off. That way, if there's ever a situation where the next one starts before the current one completes, the two invocations will be guaranteed to have different scopes.
If your "timer" is just doing an await Task.Delay, though, then there's no possibility of overlapping invocations and separate scopes aren't necessary. Some people prefer them anyway, since "invocation" and "scope" conceptually go well together.
I have followed the Microsoft documentation on how to implement a BackgroundService with a task queue, but I noticed there's no Task.Delay in the main loop, is it a concern or will it still run fine?
This is the service class in question:
public class BackgroundTasksService : BackgroundService
{
public IBackgroundTaskQueue TaskQueue { get; }
public BackgroundTasksService(IBackgroundTaskQueue taskQueue)
{
TaskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await base.StopAsync(stoppingToken);
}
}
This code was taken from
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio#queued-background-tasks
The answer lies in what is happening if this code executes while there is nothing in the queue.
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
The answer is that nothing is happening. This line of code doesn't execute over and over again until something is dequeued. That's why there's no need for a delay.
It also doesn't block until something is dequeued.
What happens is that the thread executing the method is freed to do something else. No thread is dedicated to the execution of DequeueAsync. (See this excellent post - There Is No Thread.)
When and if an item appears in the queue to be dequeued, then an available thread is assigned to resume execution of the method.
DequeueAsync only throws an exception if
the cancellation token is canceled
the queue's Complete method is called and the queue is empty, which means that you're no longer expecting anything to appear in the queue.
I have .NET Core console application containing two independent tasks that should be running in parallel for the entire life-time of the application. I was thinking to use BackgroundService:
class BackgroundTaskOne : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work one
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
class BackgroundTaskTwo : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work two
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
And register them like this:
services.AddHostedService<BackgroundTaskOne>();
services.AddHostedService<BackgroundTaskTwo>();
But these are going to run in order. So I have two questions:
Is there a way to make these two run in parallel?
Are there any other alternatives to run two long-running background processes in .NET Core in parallel?
The docs of BackgroundService.ExecuteAsync say
The implementation should return a task that represents the lifetime of the long running operation(s) being performed.
Your implementation returns a completed task when the whole work is done. In fact you implemented it to run sync and not async and that is the reason for not running parallel.
Here is a sample implementation with some fake async work:
class BackgroundTaskOne : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do work one
await Task.Delay( 100 );
}
catch (Exception e)
{
// log
}
}
}
}
As documentation says:
https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/host/hosted-services.md
No further services are started until ExecuteAsync becomes
asynchronous, such as by calling await.
As long as you do not have async clause mentioned at the ExecuteAsync method above, I suspect your method is synchronous at whole. This is why two services are called sequentially, not in parallel. Give them a break, introduce good amount of awaitable code.
I have a class registered as a singleton. In the constructor of this class, I subscribe to IOptonsMonitor.OnChange:
class Test
{
public Test(IOptionsMonitor<MyOptions> myOptionsMonitor)
{
myOptionsMonitor.OnChange(async options => await SomeWork());
}
public async Task SomeWork()
{
await Task.Delay(TimeSpan.FromSeconds(30));
}
}
In this case SomeWork task will actually "Fire and Forgot" which is probably wrong. Another approach is to run SomeWork synchronously:
myOptionsMonitor.OnChange(options => SomeWork().GetAwaiter().GetResult());
But in this case, OnChange will be blocked for a time of SomeWork execution.
Which approach will be more correct?
How is it possible to run multiple IHostedServices in parallel?
I use the WorkerService in .Net Core 3.0 and want both services to run parallel. Currently the second service is waiting for the first one to finish. Both services should run endlessly.
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ServiceA>();
services.AddHostedService<ServiceB>();
});
}
A service looks like this:
public class ServiceA : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
do
{
Console.WriteLine("Sample");
await Task.Delay(5000, stoppingToken);
} while (!stoppingToken.IsCancellationRequested);
}
}
// edit:
Very reluctantly I would use a Task.Run(() => method()); method like this. But of course this way always works:
public class ServiceA : BackgroundService
{
public override Task StartAsync(CancellationToken cancellationToken)
{
Task.Factory.StartNew(() => ExecuteAsync(cancellationToken), cancellationToken);
return Task.CompletedTask;
}
}
I asked myself a similar question and made some search but couldn't find a good answer.
I solved the issue running every background service in Task.Run with a cancellation token from BackgroundService.ExecuteAsync()
I have 2 services like you.
public class BackgroundService1: BackgroundService
{
public BackgroundService1()
{
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Task.Run(async () =>
{
await DoWork(stoppingToken);
}, stoppingToken);
return Task.CompletedTask;
}
}
//Second service is just like the first one:
public class BackgroundService2: BackgroundService
{
public BackgroundService2()
{
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Task.Run(async () =>
{
await DoWork(stoppingToken);
}, stoppingToken);
return Task.CompletedTask;
}
}
and register them in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<BackgroundService1>();
services.AddHostedService<BackgroundService2>();
})
.UseWindowsService()
I've had the same kind of issue: Multiple service that do different work at different frequencies.
When looking into it, BackgroundService seems to be designed for sequential execution (an infinite loop based service's worst enemy).
After getting a hint from this thread, I found the solution that works for my case using Microsoft's Timer Service example.
The base TimerService implements IHostedService and IAsyncDisposable:
StartAsync() starts the timer on the DoWork()
DoWork() is your overridable main work procedure.
StopAsync() stops the timer gracefully.
DisposeAsync() cleans up.
I've tested by deriving multiple TimerServices with different execution frequencies and adding them with services.AddHostedService<>();.
They all start and run at the same time, do their bit on clock.
/!\ It is not Task based as it uses timer events. Just pointing this out because I've already had quite a difficult troubleshooting experience the one time I mixed time-based events and Tasks /!\
No need to manually create a task. The default StartAsync calls ExecuteAsync and returns that task to be awaited somewhere else.
https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs#L30
So, you can do return base.StartAsync(cancellationToken) before returning Task.Completed in StartAsync.