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();
}
}
Related
I am wondering if anyone can help me I am trying to write a class which will create a task queue of tasks which runs without waiting for the result. I have a couple issues the first is I want to remove the completed tasks from the queue so that the task queue does not grow to large and when application shuts down it will clear/finish the task queue first.
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Task? workItem, CancellationToken cancellationToken);
Task<Task?> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly ConcurrentQueue<Task?> _workItems = new();
private readonly ILogger<BackgroundTaskQueue> _logger;
private readonly SemaphoreSlim _signal = new(0);
public BackgroundTaskQueue(ILogger<BackgroundTaskQueue> logger, IHostApplicationLifetime lifetime)
{
_logger = logger;
lifetime.ApplicationStopped.Register(OnStopping);
lifetime.ApplicationStopping.Register(OnStopping);
}
public void QueueBackgroundWorkItem(Task? workItem, CancellationToken cancellationToken)
{
if (workItem is null or { IsCanceled: true })
return;
_workItems.Enqueue(workItem);
_signal.Release();
}
private void OnStopping()
{
try
{
DequeueAsync(CancellationToken.None).GetAwaiter().GetResult();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "An error has occurred whilst attempting to dequeue the background work que.");
}
}
public async Task<Task?> DequeueAsync(CancellationToken cancellationToken)
{
try
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "An error has occurred whilst attempting to dequeue the background work que.");
}
return null;
}
}
I have a HostedService which inherits from BackgroundService. It loops and dequeues items from a Queue.
These items are placed on the queue via a Controller from a http request.
If I set Postman runner to fire an item every 500 miliseconds, for up to 60 items. Some early items are dequeued after seconds, but later, it can take up to 10 seconds to dequeue.
I have tried: Queue, ConcurrentQueue and BlockingCollection. All with the same results.
Any ideas? Is this a valid use case for any of the Queue types I mentioned?
Here are the implementation details:
Registration:
services.AddSimpleInjector(_container, options =>
{
options.AddAspNetCore().AddControllerActivation();
options.AddHostedService<QueuedHostedService>();
});
The Background service:
public class QueuedHostedService : BackgroundService
{
private readonly IApiLog _apiLog;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
IApiLog apiLog)
{
TaskQueue = taskQueue;
_apiLog = apiLog;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(
CancellationToken cancellationToken)
{
_apiLog.Log(new LogEntry("Queued Hosted Service is starting."));
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.Dequeue(cancellationToken);
_apiLog.Log(new LogEntry($"Dequeuing work-item: {nameof(workItem)}"));
try
{
await workItem(cancellationToken);
}
catch (Exception exception)
{
_apiLog.Log(new LogEntry(exception, $"Error occurred executing {nameof(workItem)}."));
}
}
_apiLog.Log(new LogEntry("Queued Hosted Service is stopping."));
}
}
The queue:
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);
private readonly IApiLog _apiLog;
private readonly Queue<Func<CancellationToken, Task>> _items = new Queue<Func<CancellationToken, Task>>();
public BackgroundTaskQueue(IApiLog apiLog)
{
_apiLog = apiLog;
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem, string eventId, string correlationId)
{
try
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_items.Enqueue(workItem);
_apiLog.Log(new LogEntry($"BackgroundWorkItem has been enqueued for EventId={eventId}, CorrelationId={correlationId}"));
}
catch (Exception exception)
{
_apiLog.Log(new LogEntry(exception, exception.Message));
}
finally
{
_signal.Release();
}
}
public async Task<Func<CancellationToken, Task>> Dequeue(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_items.TryDequeue(out var workItem);
return workItem;
}
}
And in the controller, the method that ultimately places items on the queue:
public void AddRequestToQueue(MyEvent myEvent, string correlationId, string userName)
{
if (string.IsNullOrEmpty(correlationId)) correlationId = Guid.NewGuid().ToString();
_apiLog.Log(new LogEntry($"Adding Update Event Request to Queue. OperationType={OperationType.ToString()}, EventId={myEvent.Id}, CorrelationId={ correlationId }"));
BackGroundTaskQueue.QueueBackgroundWorkItem(async token =>
{
_apiLog.Log(new LogEntry($"Update Event Request Dequeued. OperationType={OperationType.ToString()}, EventId={myEvent.Id}, CorrelationId={ correlationId }"));
await AddSomethingToDatabase(myEvent, correlationId, userName);
var event = _converter.Convert<SomethingElse>(myEvent);
await SendSomethingToRabbit(event, correlationId, OperationType);
}, myEvent.Id.ToString(), correlationId);
}
I am seeing up to 10 seconds between the log lines:
Adding Update Event Request to Queue
and
Update Event Request Dequeued
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)}");
}
}
}
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 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