I have a background service that runs every 30 seconds in my ASP .NET Core WebApi application. Its registered to the service container in ConfigureServices in Startup.cs by the line
services.AddSingleton<IHostedService, SimpleService>();
Then I have this class and its upper class to execute a given method every 30 seconds:
The general background service class:
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task currentTask;
private readonly CancellationTokenSource stopCts = new CancellationTokenSource();
public virtual Task StartAsync(CancellationToken cancellationToken)
{
currentTask = ExecuteAsync(stopCts.Token);
if (currentTask.IsCompleted)
return currentTask;
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (currentTask == null)
return;
try
{
stopCts.Cancel();
}
finally
{
await Task.WhenAny(currentTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
protected virtual async Task ExecuteAsync(CancellationToken cancellationToken)
{
DateTime nextExecution = DateTime.Now;
do
{
DateTime currentTime = DateTime.Now;
if (nextExecution <= currentTime)
{
nextExecution = currentTime.Add(TimeSpan.FromSeconds(30));
await Process(cancellationToken);
}
else
{
await Task.Delay(nextExecution - currentTime, cancellationToken);
}
}
while (!cancellationToken.IsCancellationRequested);
}
protected abstract Task Process(CancellationToken cancellationToken);
public void Dispose()
{
stopCts.Cancel();
}
}
The one to use when a specific scope is needed:
public abstract class ScopedBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
public ScopedBackgroundService(IServiceScopeFactory serviceScopeFactory)
{
this.serviceScopeFactory = serviceScopeFactory;
}
protected override async Task Process(CancellationToken cancellationToken)
{
using (var scope = serviceScopeFactory.CreateScope())
{
await ProcessInScope(scope.ServiceProvider, cancellationToken);
}
}
public abstract Task ProcessInScope(IServiceProvider serviceProvider, CancellationToken cancellationToken);
}
When implementing the service itself I use this class (which is also registered in the ConfigureServices method):
public class SimpleService : ScopedBackgroundService
{
private ISimpleBusiness simpleBusiness;
public SimpleService(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
}
public override async Task ProcessInScope(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
this.simpleBusiness = serviceProvider.GetService<ISimpleBusiness>();
foreach (var b in simpleBusiness.GetAll())
{
await this.simpleBusiness.Check(b);
}
}
}
This works good. But I have the problem that after each request the used memory increased. The garbage collector also doesnt get triggered. After a while and under certain circumstances I cant detect/find the memory increases rapidly in about 20 ms by up to 150 MB.
The SimpleBusiness class (implementing the interface ISimpleBusiness) uses a db context in simpleDA obtained via constructor injection:
public class SimpleBusiness : ISimpleBusiness
{
private ISimpleDA simpleDA;
private IHostingEnvironment hostingEnvironment;
...
public SimpleBusiness(ISimpleDA simpleDA, IHostingEnvironment environment, ...)
{
this.simpleDA = simpleDA;
this.hostingEnvironment = environment;
...
}
...
I guess this dbcontext and its attached objects dont get disposed/removed from memory. How can I pass the db context in a request-lifetime scope to the constructor when calling ProcessInScope in ScopedBackgroundService for the following created instances of all objects used in the underlying methods without changing the current data access and business classes/interfaces?
EDIT:
This is the memory and cpu usage over about 26 minutes.
The usage of about 26 minutes runtime
Related
I have a worker service with the base class for all workers, which accepts the IOptionsMonitor constructor parameter. This monitor contains options object instance with the execution interval value. The question is how to dynamically change the interval, even when the await Task.Delay(Interval); was called? I mean if the Interval value is set to one day, and after the Task.Delay method being called it changes, for example, to one hour - I still need to wait one day and only on the next call the delay would be updated. How I can cancel the current delay and start a new one if the property Interval value was updated?
Thanks.
Please see the code attached below:
public abstract class WorkerBase<TWorker, TWorkerOptions> : BackgroundService
where TWorker : WorkerBase<TWorker, TWorkerOptions>
where TWorkerOptions : IHaveIntervalProperty
{
protected WorkerBase(IServiceProvider serviceProvider, ILogger<TWorker> logger, IOptionsMonitor<TWorkerOptions> options)
{
_logger = logger;
_serviceProvider = serviceProvider;
_workerName = typeof(TWorker).Name;
Interval = options.CurrentValue.Interval;
options.OnChange(UpdateOptions);
}
public TimeSpan Interval { get; private set; }
public virtual void UpdateOptions(TWorkerOptions options)
=> Interval = options.Interval;
public abstract Task DoWork(IServiceProvider provider);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation(Logs.InformationWorkerRunning, _workerName, DateTime.UtcNow);
try
{
using var scope = _serviceProvider.CreateScope();
await DoWork(scope.ServiceProvider);
}
catch (Exception e)
{
_logger.LogCritical(e, e.Message);
}
finally
{
await Task.Delay(Interval, stoppingToken);
}
}
}
}
Okay, so based on #Panagiotis Kanavos comment I came up with the following code:
public abstract class RepeatableWorker<TWorker, TOptions> : IHostedService, IDisposable
where TWorker : RepeatableWorker<TWorker, TOptions>
where TOptions : IHaveIntervalProperty
{
#region Fields
private readonly IServiceProvider _serviceProvider;
private protected readonly ILogger<TWorker> _logger;
private readonly string _workerName;
private Timer? _executionTimer;
private TimeSpan _interval;
#endregion
#region Constructors
protected RepeatableWorker(IServiceProvider serviceProvider,
ILogger<TWorker> logger,
IOptionsMonitor<TOptions> options)
{
_serviceProvider = serviceProvider;
_logger = logger;
_workerName = typeof(TWorker).Name;
_interval = options.CurrentValue.Interval;
options.OnChange(UpdateOptions);
}
#endregion
#region Properties
public TimeSpan Interval
{
get => _interval;
private set
{
if (value != _interval)
{
_executionTimer?.Change(TimeSpan.Zero, value);
_interval = value;
}
}
}
#endregion
#region Public methods
public virtual void UpdateOptions(TOptions options)
=> Interval = options.Interval;
public abstract void DoWork(IServiceProvider serviceProvider);
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(Logs.InformationWorkerStarting, _workerName, DateTime.UtcNow);
_executionTimer = new(DoWorkInternal, null, TimeSpan.Zero, Interval);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation(Logs.InformationWorkerStopping, _workerName, DateTime.UtcNow);
_executionTimer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
GC.SuppressFinalize(this);
_executionTimer?.Dispose();
}
#endregion
#region Private methods
private void DoWorkInternal(object? state)
{
try
{
_logger.LogInformation("Worker {0} running at {1}.", _workerName, DateTime.UtcNow);
using var scope = _serviceProvider.CreateScope();
DoWork(scope.ServiceProvider);
}
catch (Exception e)
{
_logger.LogCritical(e, e.Message);
}
}
#endregion
}
And the IHaveIntervalProperty interface:
public interface IHaveIntervalProperty
{
TimeSpan Interval { get; set; }
}
Just in case someone will need such a solution.
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();
}
}
I'm trying to use an IHostedService as a fire-and-forget email sender in an ASP.NET Core application. It looks like the best way to do this is using the BufferBlock class; the trouble is, ReceiveAsync never finishes, even when I post new items into the BufferBlock.
Here is the HostedService base class:
public abstract class HostedService
{
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
_cts.Cancel();
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
}
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
from which my EmailService is derived as follows:
public sealed class EmailService : HostedService, IEmailService
{
private readonly ISendEmail _emailClient;
private readonly BufferBlock<MailMessage> _emailQueue;
public EmailService(ISendEmail emailClient)
{
_emailClient = emailClient;
_emailQueue = new BufferBlock<MailMessage>();
}
public void EnqueueEmail(MailMessage email)
{
var accepted = _emailQueue.Post(email);
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var nextEmail = await _emailQueue.ReceiveAsync(cancellationToken).ConfigureAwait(false);
await _emailClient.SendMailAsync(nextEmail);
}
}
}
The IEmailService interface is just a simple fire-and-forget method:
public interface IEmailService : IHostedService
{
void EnqueueEmail(MailMessage email);
}
So this should all be enough. Within my controllers, I should be able to inject an IEmailService, and then enqueue messages as required. The problem is that when I run the following test:
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(10)]
public async Task Emails_are_sent_after_they_are_enqueued(int emailCount)
{
for (var i = 0; i < emailCount; ++i)
{
_emailService.EnqueueEmail(new MailMessage());
}
await _testEmailClient.WaitForEmailsToBeSentAsync(emailCount);
}
the ReceiveAsync method never completes. I have tried using ConfigureAwait(false), but this seems to have no effect.
Within my tests, the HostedService is started by the ASP.NET Core pipeline, and ExecuteAsync is entered. I would expect ReceiveAsync to complete when an item is available in the BufferBlock, but there must be some threading subtlety I'm missing.
The problem was that my IoC container was wiring up multiple instances of IEmailService, and the instance that was calling ReceiveAsync was different to the instance that was calling Post. This is because EmailService was an instance both of IEmailService and IHostedService.
The answer was to ditch IEmailService altogether. To use EmailService in production code, I can inject an IEnumerable<IHostedService> instance, then pull my EmailService from that collection using OfType<EmailService>().First().
How to write to the database on a timer in the background. For example, check mail and add new letters to the database. In the example, I simplified the code just before writing to the database.
The class names from the example in Microsoft.
The recording class itself:
namespace EmailNews.Services
{
internal interface IScopedProcessingService
{
void DoWork();
}
internal class ScopedProcessingService : IScopedProcessingService
{
private readonly ApplicationDbContext _context;
public ScopedProcessingService(ApplicationDbContext context)
{
_context = context;
}
public void DoWork()
{
Mail mail = new Mail();
mail.Date = DateTime.Now;
mail.Note = "lala";
mail.Tema = "lala";
mail.Email = "lala";
_context.Add(mail);
_context.SaveChangesAsync();
}
}
}
Timer class:
namespace EmailNews.Services
{
#region snippet1
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(IServiceProvider services, ILogger<TimedHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
#endregion
}
Startup:
services.AddHostedService<TimedHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
It seems everything is done as in the example, but nothing is added to the database, which is not so?
This is a rather interesting question, that boils down to "How do you correctly handle an async timer callback?"
The immediate problem is that SaveChangesAsync isn't getting awaited. The DbContext almost certainly gets disposed before SaveChangesAsync has a chance to run. To await it, DoWork must become an async Task method (never async void) :
internal interface IScheduledTask
{
Task DoWorkAsync();
}
internal class MailTask : IScheduledTask
{
private readonly ApplicationDbContext _context;
public MailTask(ApplicationDbContext context)
{
_context = context;
}
public async Task DoWorkAsync()
{
var mail = new Mail
{ Date = DateTime.Now,
Note = "lala",
Tema = "lala",
Email = "lala" };
_context.Add(mail);
await _context.SaveChangesAsync();
}
}
The problem now is how to call DoWorkAsync from the timer callback. If we just call it without awaiting, we'll get the same problem we had in the first place. A timer callback can't handle methods that return Task. We can't make it async void either, because this would result in the same problem - the method will return before any async operation has a chance to finish.
David Fowler explains how to properly handle asynchronous timer callbacks in the Timer Callbacks section of his Async Guidance
article :
private readonly Timer _timer;
private readonly HttpClient _client;
public Pinger(HttpClient client)
{
_client = new HttpClient();
_timer = new Timer(Heartbeat, null, 1000, 1000);
}
public void Heartbeat(object state)
{
// Discard the result
_ = DoAsyncPing();
}
private async Task DoAsyncPing()
{
await _client.GetAsync("http://mybackend/api/ping");
}
The actual method should be async Task but the returned task only has to be assigned, not awaited, in order for it to work properly.
Applying this to the question leads to something like this :
public Task StartAsync(CancellationToken cancellationToken)
{
...
_timer = new Timer(HeartBeat, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void Heartbeat(object state)
{
_ = DoWorkAsync();
}
private async Task DoWorkAsync()
{
using (var scope = Services.CreateScope())
{
var schedTask = scope.ServiceProvider
.GetRequiredService<IScheduledTask>();
await schedTask.DoWorkAsync();
}
}
David Fowler explains why async void is ALWAY BAD in ASP.NET Core - it's not only that async actions won't be awaited, exceptions will crash the application.
He also explains why we can't use Timer(async state=>DoWorkAsync(state)) - that's an async void delegate.
Slender answered my original question about what happens to fire and forget, after the HTTP Response is sent, but Now I'm left with the question how to properly queue background tasks
EDIT
As we all know Async void is generally bad, except for in the case when it comes to event handlers, I would like to execute some background logic without have to have the client wait. My original Idea was to use Fire and Forget
Say I have an event:
public event EventHandler LongRunningTask;
And then someone subscribes a fire and forget task:
LongRunningTask += async(s, e) => { await LongNetworkOperation;};
the web api method is call:
[HttpGet]
public async IActionResult GetTask()
{
LongRunningTask?.Invoke(this, EventArgs.Empty);
return Ok();
}
But If I do this my long running task isn't guaranteed to finish, How can I handle running background task without affect the time the time it take to make my request (e.g I don't want to wait for the task to finish first)?
.NET Core 2.1 has an IHostedService, which will safely run tasks in the background. I've found an example in the documentation for QueuedHostedService which I've modified to use the BackgroundService.
public class QueuedHostedService : BackgroundService
{
private Task _backgroundTask;
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (false == stoppingToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
this._logger.LogError(ex, $"Error occurred executing {nameof(workItem)}.");
}
}
}
}
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Now we can safely queue up tasks in the background without affecting the time it takes to respond to a request.
Just wanted to add some additional notes to #johnny5 answer. Right now you can use https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/ instead of ConcurrentQueue with Semaphore.
The code will be something like this:
public class HostedService: BackgroundService
{
private readonly ILogger _logger;
private readonly ChannelReader<Stream> _channel;
public HostedService(
ILogger logger,
ChannelReader<Stream> channel)
{
_logger = logger;
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var item in _channel.ReadAllAsync(cancellationToken))
{
try
{
// do your work with data
}
catch (Exception e)
{
_logger.Error(e, "An unhandled exception occured");
}
}
}
}
[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
private readonly ChannelWriter<Stream> _channel;
public UploadController (
ChannelWriter<Stream> channel)
{
_channel = channel;
}
public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
{
var ms = new MemoryStream();
await fileInfo.FormFile.CopyToAsync(ms);
await _channel.WriteAsync(ms);
return Ok();
}
}