Fire and Forget database-related method in Hosted Service

389 views Asked by At

I'm having issues finding a solution to my problem and mostly because i don't understand them completely so, here I am asking your support.

I need to fire and forget a method that selects and updates records from database, the problem is I have a 15seconds range between record creation and its appearance in my database (synchronization issue not fixable by me, so i have to accept it) without freezing user's interface and meanwhile letting it create other records.

I tried to simply Task.Run(method) it but every time it's fired the dbContext it's refreshed so nothing is done.

Googling around I found a lot of IHostedService and BackgroundService solutions but i really can't get to the point in them: if I understand what i'm trying to do, I need to call an Hosted Service and passing them a scoped version of my dbContext so every fired method will have it's own dbContext and they could work simultaneously. But can't really get HOW TO DO that.

I managed my code in various layers and repositories, so I'll try to be as clear as possible.

Controller:

public class RMAController : BaseController
{
  private readonly ApplicationServiceRecords applicationServiceRecords;
  
  public RMAController(ApplicationServiceRecords 
          applicationServiceRecords,
          IConfiguration configuration,
          AuthenticationService authenticationService,
          AuthorizationService authorizationService) 
           : base(
          configuration, 
          authenticationService,
          authorizationService)
        {
            this.applicationService = applicationService;
            this.applicationServiceRecords= applicationServiceRecords;
        }   

[HttpPost]
[Authorize]
public async Task<IActionResult>CreateRecord(
          ResponseCreateRecord viewModel)
        {
            if (!ModelState.IsValid)
                return Content("Error X");

          //this is the method i want to fire and forget
          await ApplicationServiceRecords.CreateRecordAsync(viewModel);
           
            return RedirectToAction("TestPage");
        }
 }

Inside "CreateRecordAsync" i call other method's from Domain Layer that create, waits and update the record (again, can't get rid of waits nor create it without the need to update it immediately)

I tried using BackgroundService, this way:

public class BackgroundWorkerQueue 
{
    private ConcurrentQueue <Func< CancellationToken,Task >> _workItems = new ConcurrentQueue < Func < CancellationToken,Task >> ();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);
    public async Task < Func < CancellationToken,
    Task >> DequeueAsync(CancellationToken cancellationToken) {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out
        var workItem);
        return workItem;
    }
    public void QueueBackgroundWorkItem(Func < CancellationToken, Task > workItem) {
        if (workItem == null) {
            throw new ArgumentNullException(nameof(workItem));
        }
        _workItems.Enqueue(workItem);
        _signal.Release();
    }
}


public class LongRunningService: BackgroundService {
    private readonly BackgroundWorkerQueue queue;
    private readonly ILogger < LongRunningService > _logger;
    private readonly MyContext _dbcontext;
    public LongRunningService(BackgroundWorkerQueue queue, ILogger < LongRunningService > logger, IServiceProvider serviceProvider) {
        _logger = logger;
        _dbcontext = serviceProvider.CreateScope().ServiceProvider.GetRequiredService <MyContext > ();//thought thiw could be the solution, yet nope (probably can't get how to use it)
        this.queue = queue;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken) 
        {
        while (!stoppingToken.IsCancellationRequested) 
                {
            var workItem = await queue.DequeueAsync(stoppingToken);
            await workItem(stoppingToken);
        }
    }
}

Added them in startup:

services.AddHostedService<LongRunningService>();
services.AddSingleton<BackgroundWorkerQueue>();

And Fired the method using(?) this from controller :

_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>{ 
ApplicationServiceRecords.CreateRecordAsync(viewModel); });

But I got "Invalid attempt to call ReadAsync when reader is closed." on first attemp using DB in a simple select client = await repository.GetClienteByIdAsync(client.Id);

And that's all.

I'm sorry for bad english/ bad programming/bad explanation, and thank you in advance to everyone'll help.

0

There are 0 answers