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.
Related
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
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.
I am having issues where my DbContext is being disposed of early. It is only ever apparent when calling any of the *Async methods, such as ToListAsync() - if i call any of the syncronous methods everything is fine.
I can't figure out what I'm doing wrong.
Any advise please?
Here is as much of the code as i believe is needed.
The DbContext and its interface
public interface IMyDbContext
{
DbSet<MyModel> MyModels { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public DbSet<MyModel> MyModels { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyModelConfig());
}
}
A Repository using this DbContext
public class MyModelRepository : IMyModelRepository
{
private readonly IMyDbContext _dbContext;
private string _baseSql = "Some SQL here ";
public MyModelRepository(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IList<MyModel>> GetAllAsync(Paging paging, Permission permission)
{
if (permission == null)
throw new ArgumentNullException("permission");
string sql = ApplyFilter(_baseSql, permission);
try
{
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).Skip(paging.Skip).Take(paging.Take).ToListAsync();
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).ToListAsync();
// THIS WORKS
return await _dbContext.MyModels.FromSql(sql).ToList();
}
catch (Exception e)
{
throw new InvalidOperationException("Could not retrieve data", e);
}
}
}
I'm calling the repo via a service that looks like this:
public class GetAllMyModelQuery : IGetAllMyModelQuery
{
private readonly IMyModelRepository _myModelRepository;
private readonly IPermissionService _permissionService;
private readonly ILogger _logger;
public GetAllAbsenceQuery(IMyModelRepository myModelRepository, IPermissionService permissionService, ILogger<GetAllMyModelQuery> logger)
{
_myModelRepository = myModelRepository;
_permissionService = permissionService;
_logger = logger;
}
public async Task<IList<Emp_AbsenceEtrac>> Execute(Paging paging)
{
if (_permissionService.Permission == null)
{
_logger.LogInformation("No permission to the requested resource");
return null;
}
// if external?
// call external repo
//TODO//
// else
return await _myModelRepository.GetAllAsync(paging, _permissionService.Permission);
}
}
This in turn is called by the controller
public class MyModelController : Controller
{
private readonly IQueryStore _queryStore;
public MyModelController(IQueryStore queryStore)
{
_queryStore = queryStore;
}
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(_queryStore.GetAllMyModelQuery.Execute(paging));
}
}
Finally, it's all wired together in the startup:
services.AddScoped<IMyDbContext, MyDbContext>();
services.AddScoped<IMyModelRepository, MyModelRepository>();
// Everything else above is also added as scope..
services.AddDbContext<MyDbContext>(opts =>
{
opts.UseSqlServer(Configuration.GetConnectionString("MyDb"),
sqlServerOptions =>
{
sqlServerOptions.CommandTimeout(600);
// required to allow skip/take on sql server 2008
sqlServerOptions.UseRowNumberForPaging(true);
});
});
Is there anything jumping out that would cause my Async calls to result in a closed Db connection?
Error is:
You should await the GetAllMyModelQuery.Execute method in your Index controller action:
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(await _queryStore.GetAllMyModelQuery.Execute(paging).ConfigureAwait(false));
}
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();
}
}
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