Background task of writing to the database by timer - c#

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.

Related

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 re-enqueue a failed background async task

Good day, I'm trying to create a general purpose task queue service that executes in the background using BackgroundService. I avoided using a Delegate function Func<T1,T2, OuT> as input to EnqueueTask(Task<ResponseHelper> newTask) method because I wanted a generic solution, so I opted to pass a Task<ResponseHelper> instead. But this solution does not allow me to re-enqueue a failed task inside ExecuteAsync(CancellationToken stoppingToken) because the task returns an instance of ResponseHelper which is not a copy of the failed task. Kindly help me correct the code I have to return a dequeued task from DequeueTaskAsync(CancellationToken cancellationToken) function instead of an instance of ResponseHelper.
public interface ITaskQueueHelper
{
void EnqueueTask(Task<ResponseHelper> newTask);
Task<ResponseHelper> DequeueTaskAsync(CancellationToken cancellationToken);
}
public class TaskQueueHelper : ITaskQueueHelper
{
private readonly SemaphoreSlim signal;
private readonly ConcurrentQueue<Task<ResponseHelper>> taskQueue;
public TaskQueueHelper()
{
signal = new SemaphoreSlim(0);
taskQueue = new ConcurrentQueue<Task<ResponseHelper>>();
}
public void EnqueueTask(Task<ResponseHelper> newTask)
{
if (newTask == null)
{
throw new ArgumentNullException(nameof(newTask));
}
taskQueue.Enqueue(newTask);
signal.Release();
}
public async Task<ResponseHelper> DequeueTaskAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
taskQueue.TryDequeue(out var currentTask);
/*I need to return currentTask here, instead of an instance of ResponseHelper*/
return await currentTask;
}
}
public class TaskQueueService : BackgroundService
{
private readonly ITaskQueueHelper taskQueue;
private readonly ILogger<TaskQueueService> logger;
public TaskQueueService(
ITaskQueueHelper _taskQueue,
ILogger<TaskQueueService> _logger)
{
logger = _logger;
taskQueue = _taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
ResponseHelper response = await taskQueue.DequeueTaskAsync(stoppingToken);
try
{
if (!response.Status.Equals(ResultCode.Success))
{
/*I need to re-enqueue a failed task here*/
//taskQueue.EnqueueTask(currentTask);
}
}
catch (Exception e)
{
logger.LogError(e, $"Error occurred executing {nameof(TaskQueueService)}");
}
}
}
}
for retrying you could do:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
ResponseHelper response = await taskQueue.DequeueTaskAsync(stoppingToken);
try
{
if (!response.Status.Equals(ResultCode.Success))
{
// Retry the task.
response = await taskQueue.DequeueTaskAsync(stoppingToken);
}
}
catch (Exception e)
{
logger.LogError(e, $"Error occurred executing {nameof(TaskQueueService)}");
}
}
}

Unit testing an async method that uses System.Threading.Timer

In .NET Core, background tasks are implemented as IHostedService. This is my hosted service:
public interface IMyService {
void DoStuff();
}
public class MyHostedService : IHostedService, IDisposable
{
private const int frequency;
private readonly IMyService myService;
private Timer timer;
public MyHostedService(IMyService myService, Setting s)
{
this.myService = myService;
frequency = s.Frequency;
}
public void Dispose()
{
this.timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(this.frequency));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object state)
{
try
{
this.myService.DoStuff();
}
catch (Exception e)
{
// log
}
}
}
I am trying to unit test this class, and all I want is to make sure DoStuff gets called when StartAsync method is called. This is my unit test:
[TestFixture]
public class MyHostedServiceTests
{
[SetUp]
public void SetUp()
{
this.myService = new Mock<IMyService>();
this.hostedService = new MyHostedService(this.myService.Object, new Setting { Frequency = 60 });
}
private Mock<ImyService> myService;
private MyHostedService hostedService;
[Test]
public void StartAsync_Success()
{
this.hostedService.StartAsync(CancellationToken.None);
this.myService.Verify(x => x.DoStuff(), Times.Once);
}
}
Why is this failing?
It is failing because the async code is executing on a separate thread to the code that is verifying the expected behavior. That and the fact the the verifying code in invoked before the timer has had time to be invoked.
When testing an async method the test in most cases should also be async.
In this case you also need to let some time pass to allow the timer to invoke.
use Task.Delay to give the timer enough time to perform its function.
For example
[TestFixture]
public class MyHostedServiceTests {
[SetUp]
public void SetUp() {
this.myService = new Mock<IMyService>();
this.setting = new Setting { Frequency = 2 };
this.hostedService = new MyHostedService(this.myService.Object, setting);
}
private Mock<ImyService> myService;
private MyHostedService hostedService;
private Setting setting;
[Test]
public async Task StartAsync_Success() {
//Act
await this.hostedService.StartAsync(CancellationToken.None);
await Task.Delay(TimeSpan.FromSeconds(1));
await this.hostedService.StopAsync(CancellationToken.None);
//Assert
this.myService.Verify(x => x.DoStuff(), Times.Once);
}
}
Above example uses a shorter frequency to test the expected behavior

.Net Core Queue Background Tasks

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();
}
}

Execute hosted service in per request scope

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

Categories