You can use Queued background tasks and implement BackgroundService.
This link is useful.
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private readonly 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;
}
}
In QueueHostedService
, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService
:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
The services are registered in Startup.ConfigureServices
. The IHostedService
implementation is registered with the AddHostedService
extension method:
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
In the controller :
The IBackgroundTaskQueue
is injected into the constructor and assigned to Queue.
An IServiceScopeFactory
is injected and assigned to _serviceScopeFactory
. The factory is used to create instances of IServiceScope
, which is used to create services within a scope. A scope is created in order to use the app's AppDbContext
(a scoped service) to write database records in the IBackgroundTaskQueue
(a singleton service).
public class SomeController : Controller
{
private readonly AppDbContext _db;
private readonly ILogger _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
public SomeController(AppDbContext db, IBackgroundTaskQueue queue,
ILogger<SomeController> logger, IServiceScopeFactory serviceScopeFactory)
{
_db = db;
_logger = logger;
Queue = queue;
_serviceScopeFactory = serviceScopeFactory;
}
public IBackgroundTaskQueue Queue { get; }
[HttpPost]
public ActionResult DoSomething(PostViewModel model)
{
//////////////////////////
/*
* The code in this section should run asynchronously (in another thread I guess).
* That means the UI should not wait for any of these operations to end.
*
* */
ComputeHeavyOperations();
//////////////////////////
//the response should be returned immediatelly
return Json("Heavy operations have been triggered.");
}
private void ComputeHeavyOperations()
{
Queue.QueueBackgroundWorkItem(async token =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<AppDbContext>();
try
{
//use db to crud operation on database
db.doSomeThingOnDatabase();
await db.SaveChangesAsync();
}
catch (Exception ex)
{
_logger.LogError(ex,
"An error occurred writing to the " +
$"database. Error: {ex.Message}");
}
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
_logger.LogInformation(
"some background task have done on database successfully!");
});
} }