C# DotNet 3.x Multiple BackgroudServices do not run consistently - c#

I have a SignalR app in DotNet 3.1, kind-of a large chat app, and I am trying to add two BackgroundServices.
The BackgroundServices are setup to run for as long as the ASP.NET app runs.
The first BackgroundService has a very fast main loop (50 ms) and seems to work well.
The second BackgroundService has a much longer main loop (1000 ms) and seems to start randomly, stop executing randomly, and then re-starts executing again ... randomly. It is almost like the second bot is going to sleep, for a long period of time (30 to 90 seconds) and then wakes up again with the object state preserved.
Both BackgroundServices have the same base code with different Delays.
Is it possible to have multiple, independent, non-ending, BackgroundServices? If so, then what am I doing wrong?
I have the services registered like this ...
_services.AddSimpleInjector(_simpleInjectorContainer, options =>
{
options.AddHostedService<SecondaryBackgroundService>();
options.AddHostedService<PrimaryBackgroundService>();
// AddAspNetCore() wraps web requests in a Simple Injector scope.
options.AddAspNetCore()
// Ensure activation of a specific framework type to be created by
// Simple Injector instead of the built-in configuration system.
.AddControllerActivation()
.AddViewComponentActivation()
.AddPageModelActivation()
.AddTagHelperActivation();
});
And I have two classes (PrimaryBackgroundService/SecondaryBackgroundService) that have this ...
public class SecondaryBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await Task.Factory.StartNew(async () =>
{
// loop until a cancalation is requested
while (!cancellationToken.IsCancellationRequested)
{
//await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken);
await Task.Delay(TimeSpan.FromMilliseconds(1000), cancellationToken);
try
{
await _doWorkDelegate();
}
catch (Exception ex)
{
}
}
}, cancellationToken);
}
}
Should I setup a single BackgroundService that spins off two different Tasks; in their own threads? Should I be using IHostedService instead?
I need to make sure that the second BackgroundService runs every second. Also, I need to make sure that the second BackgroundService never impacts the faster running primary BackgroundService.
UPDATE:
I changed the code to use a Timer, as suggested, but now I am struggling with calling an async Task from a Timer event.
Here is the class I created with the different options that work and do not work.
// used this as the base: https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs
public abstract class RecurringBackgroundService : IHostedService, IDisposable
{
private Timer _timer;
protected int TimerIntervalInMilliseconds { get; set; } = 250;
// OPTION 1. This causes strange behavior; random starts and stops
/*
protected abstract Task DoRecurringWork();
private async void OnTimerCallback(object notUsedTimerState) // use "async void" for event handlers
{
try
{
await DoRecurringWork();
}
finally
{
// do a single call timer pulse
_timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
}
}
*/
// OPTION 2. This causes strange behavior; random starts and stops
/*
protected abstract Task DoRecurringWork();
private void OnTimerCallback(object notUsedTimerState)
{
try
{
var tf = new TaskFactory(System.Threading.CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
tf.StartNew(async () =>
{
await DoRecurringWork();
})
.Unwrap()
.GetAwaiter()
.GetResult();
}
finally
{
// do a single call timer pulse
_timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
}
}
*/
// OPTION 3. This works but requires the drived to have "async void"
/*
protected abstract void DoRecurringWork();
private void OnTimerCallback(object notUsedTimerState)
{
try
{
DoRecurringWork(); // use "async void" in the derived class
}
finally
{
// do a single call timer pulse
_timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
}
}
*/
// OPTION 4. This works just like OPTION 3 and allows the drived class to use a Task
protected abstract Task DoRecurringWork();
protected async void DoRecurringWorkInternal() // use "async void"
{
await DoRecurringWork();
}
private void OnTimerCallback(object notUsedTimerState)
{
try
{
DoRecurringWork();
}
finally
{
// do a single call timer pulse
_timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
}
}
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// https://stackoverflow.com/questions/684200/synchronizing-a-timer-to-prevent-overlap
// do a single call timer pulse
_timer = new Timer(OnTimerCallback, null, this.TimerIntervalInMilliseconds, Timeout.Infinite);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
try { _timer.Change(Timeout.Infinite, 0); } catch {; }
return Task.CompletedTask;
}
public void Dispose()
{
try { _timer.Change(Timeout.Infinite, 0); } catch {; }
try { _timer.Dispose(); } catch {; }
}
}
Is OPTION 3 and/or OPTION 4 correct?
I have confirmed that OPTION 3 and OPTION 4 are overlapping. How can I stop them from overlapping? (UPDATE: use OPTION 1)
UPDATE
Looks like OPTION 1 was correct after all.
Stephen Cleary was correct. After digging and digging into the code I did find a Task that was stalling the execution under the _doWorkDelegate() method. The random starts and stops was caused by an HTTP call that was failing. Once I fixed that (with a fire-and-forget) OPTION 1 started working as expected.

I would recommend writing two timed background tasks as shown in the documentation
Timed background tasks documentation
then they are independent and isolated.
public class PrimaryBackgroundService : IHostedService, IDisposable
{
private readonly ILogger<PrimaryBackgroundService> _logger;
private Timer _timer;
public PrimaryBackgroundService(ILogger<PrimaryBackgroundService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("PrimaryBackgroundService StartAsync");
TimeSpan waitTillStart = TimeSpan.Zero;
TimeSpan intervalBetweenWork = TimeSpan.FromMilliseconds(50);
_timer = new Timer(DoWork, null, waitTillStart, intervalBetweenWork);
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("PrimaryBackgroundService DoWork");
// ... do work
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("PrimaryBackgroundService is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
create the SecondaryBackgroundService using similar code and register them as you did before
options.AddHostedService<SecondaryBackgroundService>();
options.AddHostedService<PrimaryBackgroundService>();
Note that if you want to use any dependency injection then you have to inject IServiceScopeFactory into the background service constructor and call scopeFactory.CreateScope()

Related

ASP.NET BackgroundService - Only run sequential

I have a BackgroundService that I start from an API Controller.
There should never be more than one BackgroundService running.
How can I check if a job is already running? So I don't start a new?
API to start a new job and related code
[HttpPost]
public async Task<IActionResult> RunJob(JobMessage msg)
{
if (_queue.Count > 0)
{
return StatusCode(429, "DocumentDistributor are running. Try again later");
}
await _queue.Queue(msg);
return Ok("DocumentDistributor will start in about one minute.");
}
public interface IBackgroundTaskQueue
{
Task Queue(JobMessage message);
Task<JobMessage> Dequeue();
public int Count { get; }
}
public sealed class QueuedHostedService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public QueuedHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _serviceProvider.CreateScope();
var calculator = scope.ServiceProvider.GetRequiredService<QueueDocumentDistributor>();
await calculator.RunService();
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
catch (Exception e)
{
Log.Error(e, "Error in QueuedHostedService");
}
// check queue every 1 minute
await Task.Delay(1000 * 60, stoppingToken);
}
}
}
public class QueueDocumentDistributor
{
private readonly IBackgroundTaskQueue _queue;
private readonly ReportService _service;
public QueueDocumentDistributor(IBackgroundTaskQueue queue, ReportService service)
{
_queue = queue;
_service = service;
}
public async Task RunService()
{
var message = await _queue.Dequeue();
if (message == null) return;
await _service.CreateReports(message);
}
}
AddHostedService adds singleton instance of IHostedService, so if there is no parallel processing in the implementation framework guarantees the single job execution.
There should never be more than one BackgroundService running.
There will be only single instance of background service per type running.
How can I check if a job is already running?
Depends on what do you mean by "job". If BackgroundService - then it is started by the framework. If your some custom payload in queue - then you will need to implement some monitoring manually.
So I don't start a new?
You don't start (usually) background service manually. If QueueDocumentDistributor.RunService gurantees single execution of your logic at a time - your are fine.
Based on provided implementation looks like a single queue element is processed at a time.
Read more:
Background tasks with hosted services in ASP.NET Core

Periodic Timer force to run every second

I create the periodic timer which run under background service
public class PeriodicHostedService : BackgroundService
{
private readonly TimeSpan period = TimeSpan.FromSeconds(1);
private readonly ILogger<PeriodicHostedService> logger;
private readonly IServiceScopeFactory factory;
private int executionCount = 0;
public PeriodicHostedService(ILogger<PeriodicHostedService> logger, IServiceScopeFactory factory)
{
this.logger=logger;
this.factory=factory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using PeriodicTimer timer = new(period);
using var scope = factory.CreateScope();
ITimerJob job = scope.ServiceProvider.GetRequiredService<ITimerJob>();
while (
!stoppingToken.IsCancellationRequested &&
await timer.WaitForNextTickAsync(stoppingToken))
{
try
{
await job.ProcessAsync();
executionCount++;
logger.LogInformation($"Executed PeriodicHostedService - Count: {executionCount}");
}
catch (Exception ex)
{
logger.LogInformation($"Failed to execute PeriodicHostedService with exception message {ex.Message}. Good luck next round!");
}
}
}
}
I have set the timer run every second
however, I have job in timer need to run over 1 second just an example
internal class TimerJob : ITimerJob
{
private int runningID;
public async Task ProcessAsync()
{
runningID++;
Console.WriteLine($"{DateTime.Now} > Current Running ID : {runningID}");
await LongTimeJob();
}
private async Task LongTimeJob ()
{
Console.WriteLine($"{DateTime.Now} > Step1 Async Job End ID : {runningID}");
await Task.Delay(3000).ConfigureAwait(false);
}
}
can I know how to write the timer which force to execute on every second (and let longtime job continue work)
Thank you
you can chose not to await the job.ProcessAsync() which would allow your code to continue waiting for the next tick.
_ = job.ProcessAsync();
I must admit, running jobs every minute that are likely to run long might become a resource hog eventually. You should check your design for any unwanted side effects.

How to run multiple task in background service in .net core with different timer duration

I am creating a worker service which will be run as a windows service. I have a requirement where I would like to invoke two tasks which may have different timers.
Say DoWork should be called every 5 minutes and DoAnotherWork should be called every 10 minutes or so. These two tasks can run in parallel and are not dependant on each other.
I was able to create task DoWork which can run after every 5 minutes. I am a bit confused about how to implement another task that will have different timer duration?
public class Worker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private IDataLoaderService _dataLoaderService;
public override Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _scopeFactory.CreateScope();
_dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await DoWork(stoppingToken, _dataLoaderService);
await Task.Delay(300000, stoppingToken); //Run every 5 minutes
await DoAnotherWork(stoppingToken, _dataLoaderService);
await Task.Delay(600000, stoppingToken); //Run every 10 minutes
}
}
private async Task DoWork(CancellationToken stoppingToken, IDataLoaderService loaderService)
{
await loaderService.Process();
}
private async Task DoAnotherWork(CancellationToken stoppingToken, IDataLoaderService loaderService)
{
await loaderService.Validate();
}
}
These two tasks can run in parallel and are not dependant on each other.
Sounds to me like you have two services:
public class ProcessDataLoaderWorker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = _scopeFactory.CreateScope();
var dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();
while (true)
{
await dataLoaderService.Process();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); //Run every 5 minutes
}
}
}
public class ValidateDataLoaderWorker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = _scopeFactory.CreateScope();
var dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();
while (true)
{
await dataLoaderService.Validate();
await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); //Run every 10 minutes
}
}
}
I also modified the way the IDataLoaderService was used so that it is not used outside its scope, and changed the Task.Delay arguments to be more self-explanatory.
If you don't want to use existing scheduling libraries in your case you can go with having two timers, like in this docs, where System.Threading.Timer is utilized. Something like that:
public class Worker : IHostedService, IDisposable
{
private readonly IServiceScopeFactory _scopeFactory;
private IDataLoaderService _dataLoaderService;
private Timer _timer1;
private Timer _timer2;
public Task StartAsync(CancellationToken cancellationToken)
{
_dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();
_timer1 = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(300));
_timer2 = new Timer(DoAnotherWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(600));
return Task.CompletedTask;
}
private async void DoWork(object _)
{
// or create scope and resolve here
await _loaderService.Process();
}
private async void DoAnotherWork(object _)
{
// or create scope and resolve here
await _loaderService.Validate();
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer1?.Change(Timeout.Infinite, 0);
_timer2?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer1?.Dispose();
_timer2?.Dispose();
}
}

How to keep a hosted service alive in asp net core 3.1?

how are you?.
I have a web api in net core 3.1, this in turn contains a service that every X minutes has to run to perform data migrations (I'm just testing it), but I have 2 problems.
For the service to run, I must first run some url of my apis. The question is: How can I make this service start automatically, without the need to run any api?
When I stop using the apis for a few minutes, the service stops working. The question is: How can I keep the service "Forever" alive?
I must emphasize that my web api is hosted in a web hosting, where I do not have access to all the features of IIS
This is my code, and in advance I appreciate your help.
MySuperService.cs
public class MySuperService : IHostedService, IDisposable
{
private bool _stopping;
private Task _backgroundTask;
private static readonly log4net.ILog log =log4net.LogManager.GetLogger(typeof(MySuperService));
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("MySuperService is starting.");
log.Info("MySuperService is starting.");
_backgroundTask = BackgroundTask();
return Task.CompletedTask;
}
private async Task BackgroundTask()
{
int contador = 1;
while (!_stopping)
{
Console.WriteLine("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
log.Info("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await Task.Delay(TimeSpan.FromMinutes(3));
contador++;
}
Console.WriteLine("MySuperService background task is stopping.");
log.Info("MySuperService background task is stopping.");
}
public async Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("MySuperService is stopping.");
log.Info("MySuperService is stopping.");
_stopping = true;
if (_backgroundTask != null)
{
// TODO: cancellation
await BackgroundTask();
//await _backgroundTask;
}
}
public void Dispose()
{
Console.WriteLine("MySuperService is disposing.");
log.Info("MySuperService is disposing.");
}
}
Program.cs
public class Program
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Program));
public static void Main(string[] args)
{
...
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MySuperService>();
});
}
Inherit from BackgroundService instead of implemented IHostedService. That will take care of the machinery of starting, running and stopping your service for you.
However the problem you are facing is that IIS isn't starting your c# process until the first request of the service. Then the default application pool settings will shut it down again if there are no requests. I'd suggest setting up some kind of scheduled task to periodically request a url and monitor that the service is running. You'll want to be notified if it stops anyway right?
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio
If you inherit your infinite job service from the BackgroundService class and implement your logic inside a loop and the needed
await Task.Delay(TimeSpan.FromMinutes(x miutes))
the job will run as soon as the application starts without any API call, and stops when the app stops.
public class MyService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("test");
//await run job
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("start");
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("stop");
return Task.CompletedTask;
}
}
The approach I came up with goes as follows:
public class PingPongHostedService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine(">>>>> Hosted service starting at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());
int count = 0;
try
{
Task.Run(async () => { // run in background and return completed task
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1_766, cancellationToken);
Console.WriteLine("loop no. {0}", ++count);
}
}, cancellationToken);
}
catch (OperationCanceledException e){} // Prevent throwing if the Delay is cancelled
return Task.CompletedTask; // return ASAP
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine(">>>>> Hosted service stopped at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());
return Task.CompletedTask;
}
}
The important thing here is: StartAsync must exit ASAP, so that IGenericHost bootstrap can continue. That's why I'm using Task.run to transfer real work to another thread, allowing caller thread to continue.
I'm well aware that it is quite an old question and there have been already a few answers already but I would like to provide my input on the matter. I have used a brilliant library to schedule tasks called FluentScheduler. Before we start, add this in your Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<AppLifetimeEventsService>();
}
This is how I solved the above issue:
public class AppLifetimeEventsService : IHostedService
{
private readonly ILogger _logger;
public AppLifetimeEventsService(IServiceProvider services)
{
_logger = services.GetRequiredService<ILogger<AppLifetimeEventsService>>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The Web API has been started...");
//FluentScheduler
var registry = new Registry();
//For example let's run our method every 1 hour or 10 seconds
registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(1).Hours();
//registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(10).Seconds();
//FluentScheduler
JobManager.Initialize(registry);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Needed to remove all jobs from our Job manager when our Web API is shutting down
JobManager.RemoveAllJobs();
_logger.LogInformation("The Web API is stopping now...");
return Task.CompletedTask;
}
private async Task SomeBackgroundTask()
{
//Your long task goes here... In my case, I used a method with await here.
}
}
If you want to run a method every X seconds/minutes/hours/days/etc. you will have to implement IHostedService: info and docs. However, if you want to execute the method only once, you should implement BackgroundService.

Why is this CancellationToken getting cancelled?

It seems like right after I call my first async method (GetBar() in this example), the CancellationToken's IsCancellationRequested is set to true, but I don't want that and don't understand why it's happening.
This is in an Azure Cloud Service worker role, if that matters.
public class WorkerRole : RoleEntryPoint
{
private CancellationTokenSource cancellationTokenSource;
private Task runTask;
public override void Run()
{
this.cancellationTokenSource = new CancellationTokenSource();
this.runTask = Task.Run(() => Foo.Bar(this.cancellationTokenSource.Token), this.cancellationTokenSource.Token);
}
public override void OnStop()
{
this.cancellationTokenSource.Cancel();
try
{
this.runTask.Wait();
}
catch (Exception e)
{
Logger.Error(e, e.Message);
}
base.OnStop();
}
// ... OnStart omitted
}
public static class Foo
{
public static async Bar(CancellationToken token)
{
while (true)
{
try
{
token.ThrowIfCancellationRequested();
var bar = await FooService.GetBar().ConfigureAwait(false);
// Now token.IsCancellationRequested == true. Why? The above call does not take the token as input.
}
catch (OperationCanceledException)
{
// ... Handling
}
}
}
}
I've successfully used CancellationTokens once before in another project and I use a similar setup here. The only difference I'm aware of is that this is in an Azure Cloud Service. Any idea why IsCancellationRequested is getting set to true?
It appears OnStop was called while you where awaiting for FooService.GetBar() to complete. Perhaps add some form of logging to see if OnStop is called between the token.ThrowIfCancellationRequested(); and after the var bar = await ... returns to confirm.
That is what is causing the token to be canceled.
To solve the problem you need to make sure the overridden Run method does not return till the work is complete.
public override void Run()
{
this.cancellationTokenSource = new CancellationTokenSource();
this.runTask = Task.Run(() => Foo.Bar(this.cancellationTokenSource.Token), this.cancellationTokenSource.Token);
this.runTask.Wait(); //You may need a try/catch around it
}

Categories