A long-term task in ApiController (using WebAPI, self-hosted OWIN)

I would like to run a long-running task (say 4-5 minutes) in ApiController in a standalone OWIN environment. However, I would like to send a response after starting this task (as soon as I started a long-term task), without waiting for completion. This long-term task has nothing to do with HTTP and sequentially runs some methods that can take a very long time.

I am watching this blog and decided to try QueueBackgroundWorkItem . However, I'm not sure if you can use this method in an owin standalone host (console application) environment or use it. In a self-serving console application, I think the application itself manages requests and runs the entire request within the same AppDomain (the default application is AppDomain, we do not create any new appDomain), so maybe I can just run a long-running task in a carefree mode, without doing anything special?

Anyway, when I use QueueBackgroundWorkItem , I always get the error:

 <Error> <Message>An error has occurred.</Message> <ExceptionMessage> Operation is not valid due to the current state of the object. </ExceptionMessage> <ExceptionType>System.InvalidOperationException</ExceptionType> <StackTrace> at System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(Func`2 workItem) at BenchMarkService.SmokeTestController.IsItWorking() in C:\Devel\Code\Projects\BenchMarkService\BenchMarkService\SmokeTestController.cs:line 18 at lambda_method(Closure , Object , Object[] ) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext() </StackTrace> </Error> 

And I found this question about SO, and, to be honest, it seems a bit confusing to me, since the only way to achieve this is IRegisteredObject or not?

I'm just trying to run a multi-year task in a self-service application, and I appreciate any ideas about this. Almost all of the resources I found are based on asp.net, and I'm not sure where to start.

+5
source share
5 answers

This is pretty much what Hangfire created ( https://www.hangfire.io/ ).

Sounds like working with fire and oblivion.

 var jobId = BackgroundJob.Enqueue( () => Console.WriteLine("Fire-and-forget!")); 
+3
source

OWIN stand-alone applications are usually regular console applications, so you can unscrew some lengthy task in the same way as you would in a console application, for example:

  • Task.Run
  • ThreadPool.QueueUserWorkItem
  • new Thread(...).Start()

In ASP.NET IIS applications, it is not recommended to use such methods because application pools are usually processed, so your application closes and restarts quite often. In this case, the background task will be aborted. To prevent (or delay), APIs such as QueueBackgroundWorkItem have been introduced.

However, since you are hosted on your own and do not run in IIS, you can simply use the above APIs.

+2
source

You need to provide a mechanism for adding a task to something outside the controller.

Usually in applications I use owin for the real host, so I realized that I can use "Hosted Processes", but in a console application this will probably be much simpler.

The most obvious approach that comes to mind is to pass in some kind of global object / singleton that your DI infrastructure knows about, it can always be the same object as the "job container"

 public class FooController : ApiController { ITaskRunner runner; public FooController(ITaskRunner runner) { this.runner = runner; } Public IActionResult DoStuff() { runner.AddTask(() => { Stuff(); }); return Ok(); } } 

A task runner could accomplish this task, being a slightly simpler shell around Task.Run (), if you want to, but the presence of this hook is passed there, meaning that after the request is “processed” and the controller clears the task as before is considered “in scope,” so the application may continue to process it, rather than trying to clear it.

+2
source

I wanted to comment on the answer to the war, but answering my own question seems like a longer explanation. This may not be the complete answer, sorry.

A military solution is a possible way to implement this function, and I did something similar to this. Basically, create a task repository, and whenever a new task starts or ends, add / remove this task to the task repository. However, I would like to mention some problems, because I think that it is not as simple as that.

1) I agree with using the dependency injection framework to implement a singleton pattern. For this, I used the SingleInstance () method for Autofac. However, as you can see in many answers and resources on the Internet, a singleton pattern is not popular, although it sometimes seems necessary.

2) Your repository should be thread safe, so adding / removing tasks from this task store should be thread safe. You can use a parallel data structure, such as "ConcurrentDictionary", instead of locking (which is an expensive operation in terms of CPU time).

3) You can use the CancellationToken for your asynchronous use. operations. It is possible that your application may be disabled by the user or an exception that may be handled may occur at run time. Long-term tasks can be a bottleneck for elegant shutdown / restart operations, especially if you have sensitive data to lose and use various resources, such as files, that can be closed properly in this case. Although asynchronous. methods cannot be immediately canceled whenever you try to cancel a task using a token, it is still worth trying a CancellationToken or similar mechanisms to stop / cancel your lengthy tasks when necessary.

4) Just adding a new task to the data warehouse is not enough, you also deleted the task, even if it completed successfully or not. I tried to fire an event that signals the completion of a task, and then I deleted this task from the task store. However, I am not happy with this because dismissing the event and then attaching the method to this event to get the task to complete is more or less like a continuation in TPL. This is how to reinvent the wheel, as well as events can cause hidden problems in a multi-threaded environment.

Hope this helps. I prefer the exchange of experience rather than the publication of code, because I do not think that I have a final solution to this problem. The answer to the war gives an approximate idea, but before you do this in a production-ready system, you need to consider many problems.

Edit: For long-running tasks, you can take a look at this thread. It is good practice to manage a thread pool.

+2
source

PushStreamContent may be the answer you are looking for. PushStreamContent helps transmit the response. Looking at the next blog post to get an idea of ​​the implementation. DATA CONSUMPTION WITH ASP.NET WEB API AND PUSHCONTENTSTREAM

+2
source

All Articles