1

I have the following situation: I have an ASP.NET Core controller (.NET 5.0), which has an Import endpoint. Upon calling this endpoint it gathers some configuration and calls

MyService.Import(...) 

MyService.Import performs some time-consuming operations - it retrieves data from external endpoints (with paging and retries), and then does some heavy lifting on the database.

The controller method can't afford to wait for the service method to finish. It would simply take too long and the callers of my endpoint would get a timeout error. And it's not supposed to anyway, MyService has its own ways of reporting about the status of the import - via logs. This is supposed to be a fire-and-forget call.

I've tried to achieve this fire-and-forget behavior in the following ways:

  1. Make the service method async and just don't await it. The warning I get from this says "Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the await operator to the result of the call." So I would expect that to be the behavior - the calling (controller) method continues and reaches return Ok(), while the service method does its thing. This isn't what happens. The controller method just stands still waiting for the service method it ostensibly isn't awaiting.

  2. Await the method, but put ConfigureAwait(false) at the end. Same result as above.

  3. Calling the service method on a different thread via Task.Run(() => MyService.Import(...). This approach actually allows the controller method to finish without waiting for the service method. But this causes errors further down the pipe. Somewhere in the bowels of the code, through many layers of DI-initialized providers and resolvers, something was broken by switching the thread, and I get the following error:

    System.ObjectDisposedException: '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.
    Object name: MyDbContext

Re-working the infrastructure so that it can handle switching threads is probably a project for a week. Is there a way have the controller method return without threading? Or some way to "pass the entire context" to the new thread?

3
  • 1
    Framework or Core (the model is somewhat different). Don't forget that awaiting something doesn't mean "waiting for something" Commented Feb 12, 2023 at 0:28
  • .NET 5.0 I'll add this to the question. Commented Feb 12, 2023 at 0:40
  • 1
    The best solution is a basic distributed architecture; any fire-and-forget solution is going to lose work eventually. Commented Feb 12, 2023 at 1:17

2 Answers 2

3

. But this causes errors further down the pipe. Somewhere in the bowels of the code,

If you kick of an async task it can't access any resources tied to the HTTP request lifecycle, like your DbContext or other scoped services. It needs to create its own scope, or simply create a new instance of your DbContext for the long-running task.

Sign up to request clarification or add additional context in comments.

Comments

2

So I dug through the code and my colleague actually solved it the following way:

Response.OnCompleted(async () => MyService.Import(... return Ok(); 

This obviously works only for this specific case. I'm not sure how "correct" this is, or if there is some resource on the server that's stuck waiting for MyService.Import, but it does achieve the desired behavior wherein the controller method returns a response without waiting for the service to finish.

2 Comments

That looks like an excellent solution.
Bit risky as ASP.Net might decide to recycle the application pool if there is no in-flight request. You really should use a BackgroundWorker

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.