Home > Blockchain >  Run heavy task that uses dbContext on the API background without await
Run heavy task that uses dbContext on the API background without await

Time:07-08

my C# .Net 6 API project has a reporting requirement: Convert any query or class to CSV file.

The way that I've got the tasks is as follows:

  1. in a [POST] request endpoint named export create the CSV file from a query and upload it to blob storage, without making the user wait for the task to finish.
  2. once the controller gets the requests start the task and return 200 immediately.
  3. later on frontEnd will make a get request and ask for the document, if the document is done, return the document URL.

This is the endpoint code that I have so far:

[HttpPost("export")]
        public virtual async Task<IActionResult> Export([FromQuery] UrlRequestBase? urlRequestBase,
            [FromBody] BodyRequestBase? bodyRequestBase)
        {
            object? response;

            int status = 200;
            try
            {
                await urlRequestBase.Parse(this);

                await bodyRequestBase.Parse();

                //Run the export creation in another thread

                Task.Run(() => _repositoryBase.CreateExport(urlRequestBase, bodyRequestBase));


                return StatusCode(status);

            }
            catch (ExceptionBase ex)
            {
                return StatusCode(ex.CodeResult, ex.CreateResponseFromException());
            }


        }

The problem is that when I try to make a query inside the repository the dbContext is disposed of because of the lifetime of the DI container, so I get the following error:

Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

it only works when I add await operator but is intended to not wait this time.

How can I run this type of heavy task without await operator and still use the dbContext?

Exists a better way to do it?

CodePudding user response:

You can use the HostingEnvironment.QueueBackgroundWorkItem functionality of ASP.net

There are lots of docs on that, but here's the one that looks good to me:

https://www.c-sharpcorner.com/article/run-background-task-using-hostingenvironment-queuebackgroundworkitem-net-framew/

And steps that might work:

  • design your worker class with public properties for the query and for a task ID.
  • Generate a unique task ID somehow, e.g., GUID or counter
  • 'new up' your workout BEFORE the call to QueueBackgroundWorker... so you can set the task ID and query
  • Queue the item for work
  • Insert the unique task ID into a table (or equivalent) with a blank URL and/or status or progress fields
  • Return the task ID to the client, end the web request. This is so the client can save it to ask for the correct document.

Worker logic

  • Worker will run the query and store the results in the blob storage.
  • Worker updates row for task ID and status/progress and fills in the URL

Now client behavior:

  • calls a different web API method with the task ID
  • server reads the data table for that ID, says 'not found', 'in progress', 'errored', 'done-here's the URL', etc.

While I was typing, 'tia' made a comment about a similar service, I don't think it's exactly the same thing and if it isn't, you could use the same design as these bullets as it appears to offer similar functionality.

Study the docs and see which might be better for you, asp.net has some cool toys as you can see!

CodePudding user response:

The FastAI response was so helpful but unfortunately, I had almost no time to implement it.

DavidG recommended me to use HangFire and now my issue is finally solved

[HttpPost("export")]
        public virtual async Task<IActionResult> Export([FromQuery] UrlRequestBase? urlRequestBase,
            [FromBody] BodyRequestBase? bodyRequestBase)
        {
            object? response;
            int status = 200;
            try
            {
                await urlRequestBase.Parse(this);

                await bodyRequestBase.Parse();
//HangFire solution
                _backgroundJobClient.Enqueue(() => _repositoryBase.CreateExport(urlRequestBase, bodyRequestBase));
                

                return StatusCode(status);
            }
            catch (ExceptionBase ex)
            {
                return StatusCode(ex.CodeResult, ex.CreateResponseFromException());
            }

            return StatusCode(status, await ResponseBase.Response(response));
        }

Thank you, for taking the time to help me!

  • Related