You still can use IHostedService as base for background tasks in combination with BlockingCollection.
Create a wrapper for BlockingCollection so we can inject it as singleton.
BlockingCollection.Take will not consume processor time when collection is empty. Passing cancellation token to the .Take method will gracefully exit when token is cancelled.
public class TasksToRun
{
private readonly BlockingCollection<SingleTaskData> _tasks;
public TasksToRun() => _tasks = new BlockingCollection<SingleTaskData>(new ConcurrentQueue<SingleTaskData>());
public void Enqueue(SingleTaskData taskData) => _tasks.Add(settings);
public TaskSettings Dequeue(CancellationToken token) => _tasks.Take(token);
}
For background process we can use “built-in” implementation of IHostedService – Microsoft.Extensions.Hosting.BackgroundService.
This service will consume tasks extracted from the “queue”.
public class TaskProcessor : BackgroundService
{
private readonly TasksToRun _tasks;
public TaskProcessor(TasksToRun tasks) => _tasks = tasks;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield(); // This will prevent background service from blocking start up of application
while (cancellationToken.IsCancellationRequested == false)
{
try
{
var taskToRun = _tasks.Dequeue(_tokenSource.Token);
await ExecuteTask(taskToRun);
}
catch (OperationCanceledException)
{
// execution cancelled
}
catch (Exception e)
{
// Catch and log all exceptions,
// So we can continue processing other tasks
}
}
}
}
Then we can add new tasks from the controller without waiting for them to complete
public class JobController : Controller
{
private readonly TasksToRun _tasks;
public JobController(TasksToRun tasks) => _tasks = tasks;
public IActionResult PostJob()
{
var taskData = CreateSingleTaskData();
_tasks.Enqueue(taskData);
return Ok();
}
}
Wrapper for blocking collection should be registered for dependency injection as singleton
services.AddSingleton<TasksToRun, TasksToRun>();
Register background service
services.AddHostedService<TaskProcessor>();