简体   繁体   English

ApiController中的长时间运行任务(使用WebAPI,自托管OWIN)

[英]Long running task in ApiController (using WebAPI, self-hosted OWIN)

I would like to run a long running task (say 4-5 minutes) in an ApiController within a self-hosted OWIN environment. 我想在自托管的OWIN环境中的ApiController中运行一个长时间运行的任务(比如4-5分钟)。 However I would like to send a response back after starting that task (as soon as I started the long running task) without waiting it to finish. 但是我想在启动该任务后(在我开始长时间运行的任务时)发回响应,而不等待它完成。 This long running task has nothing to do with the HTTP and sequentially runs some methods which may take very long. 这个长时间运行的任务与HTTP无关,并且顺序运行一些可能需要很长时间的方法。

I have a look at this blog post and decided to give a try to QueueBackgroundWorkItem . 我看一下这篇博客文章并决定尝试QueueBackgroundWorkItem However, I am not sure if it is possible to use this method in a self-hosted (console application) owin environment or necessary to use it. 但是,我不确定是否可以在自托管(控制台应用程序)owin环境中使用此方法或使用它。 In a self-hosted console application I think, the application itself manage the requests and all request run within the same AppDomain (applications default AppDomain, we do not create any new appDomain), so may be I can just run a long running task in a fire-and-forget fashion, without doing anything special? 在我认为的自托管控制台应用程序中,应用程序本身管理请求,并且所有请求都在同一个AppDomain中运行(应用程序默认的AppDomain,我们不创建任何新的appDomain),因此我可以运行一个长时间运行的任务一种昙花一现的时尚,没有做任何特别的事情?

Anyway, when I use QueueBackgroundWorkItem , I always get the error: 无论如何,当我使用QueueBackgroundWorkItem ,我总是得到错误:

<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>

Also I found this question on SO and to be honest it seems a little bit confusing to me as the only way to achieve that is IRegisteredObject or not? 另外我在SO上发现了这个问题并且说实话它对我来说似乎有点混乱,因为实现它的唯一方法是IRegisteredObject与否?

I am just trying to run a long running task in a self-hosted application and I appreciate any ideas about that. 我只是想在自托管应用程序中运行一个长时间运行的任务,我很欣赏任何有关它的想法。 Almost all of the resources, questions I found are based on asp .net and I am not sure where to start. 几乎所有的资源,我发现的问题都基于asp.net,我不知道从哪里开始。

This is pretty much what Hangfire was created to do ( https://www.hangfire.io/ ). 这几乎就是Hangfire的创建方式( https://www.hangfire.io/ )。

Sounds like a fire-and-forget job. 听起来像是一场难以忘怀的工作。

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

Self-hosted OWIN applications are typically regular console applications, therefore you can spin off some long running task in the same way you would do that in a console application, eg: 自托管OWIN应用程序通常是常规控制台应用程序,因此您可以像在控制台应用程序中那样分离一些长时间运行的任务,例如:

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

In IIS hosted ASP.NET applications it is not advisable to use such methods because application pools are commonly recycled, hence your application gets shut down and restarted pretty often. 在IIS托管的ASP.NET应用程序中,不建议使用此类方法,因为应用程序池通常是循环使用的,因此应用程序会经常关闭并重新启动。 In such a case your background task would be aborted. 在这种情况下,您的后台任务将被中止。 In order to prevent (or postpone) that, APIs like QueueBackgroundWorkItem have been introduced. 为了防止(或推迟),引入了像QueueBackgroundWorkItem这样的API。

However because you are self-hosted and do not run in IIS, you can just use the APIs listed above. 但是,由于您是自托管的并且不在IIS中运行,因此您可以使用上面列出的API。

You need to provide a mechanism for adding a task to something outside of the controller. 您需要提供一种机制,用于将任务添加到控制器之外的某些内容。

Typically in the apps I use owin for the actual host is IIS so I figured out I could use "Hosted Processes" but in a console app it's likely to be a lot simpler. 通常在应用程序中我使用owin作为实际的主机是IIS,所以我想出我可以使用“托管进程”,但在控制台应用程序中,它可能会更简单。

The most obvious approach that comes to mind is to pass in some sort of global object / singleton that your DI framework knows about can be sure is always the same object as a "container for the job" 想到的最明显的方法是传递某种全局对象/单例,你的DI框架知道可以确定它总是与“工作容器”相同的对象

public class FooController : ApiController
{
    ITaskRunner runner;

    public FooController(ITaskRunner runner) { this.runner = runner; }

   Public IActionResult DoStuff() {
       runner.AddTask(() => { Stuff(); });
       return Ok();
   }
}

The task runner could do the job by being little more than a simple wrapper around Task.Run() if you wanted, but having that hook there and passed in means that after the request is "handled" and the controller gets cleaned up, the task is still considered "in scope" so the application can continue to process it instead of attempting to clean it up. 任务运行器只需要一个简单的Task.Run()包装就可以完成这项工作,但是在那里挂钩并传入它意味着在请求被“处理”并且控制器被清理之后,任务仍被视为“在范围内”,因此应用程序可以继续处理它,而不是尝试清理它。

I wanted to comment on the answer of War, but answering my own question seem better for a long explanation. 我想评论战争的答案,但回答我自己的问题似乎更好的解释。 This may not be a complete answer, sorry. 抱歉,这可能不是一个完整的答案。

War's solution is a possible way to implement this feature and I did something similar to that. War的解决方案是实现此功能的一种可能方式,我做了类似的事情。 Basically, create a task store and whenever a new task is fired or completed add/remove this task to the task store. 基本上,创建任务存储,无论何时触发或完成新任务,都将此任务添加/删除到任务存储。 However, I would like to mention some issues, because I believe its not as simple as like that. 但是,我想提一些问题,因为我认为它并不像那样简单。

1) I agree with using a dependency injection framework in order to implement a singleton pattern. 1)我同意使用依赖注入框架来实现单例模式。 I used 'SingleInstance()' method of Autofac to do that. 我使用Autofac的'SingleInstance()'方法来做到这一点。 However, as you can see in many answers and resources on the Internet, singleton pattern is not popular pattern although sometimes seems to be indispensable. 但是,正如你在互联网上的许多答案和资源中看到的那样,单身模式并不是流行的模式,尽管有时似乎是不可或缺的。

2) Your repository should be thread-safe, so adding/removing tasks from that task store should be thread-safe. 2)您的存储库应该是线程安全的,因此从该任务存储添加/删除任务应该是线程安全的。 You can use a concurrent data structure like 'ConcurrentDictionary' instead of locking (which is a expensive operation in terms of cpu time). 您可以使用并发数据结构,如“ConcurrentDictionary”而不是锁定(就cpu时间而言,这是一项昂贵的操作)。

3) You may consider using CancellationToken for your async. 3)您可以考虑使用CancellationToken进行异步。 operations. 操作。 It is highly possible that your application may be shutdown by user or an exception which can be handled may occur during the runtime. 您的应用程序很可能被用户关闭,或者在运行时可能会发生异常。 Long running tasks may be a bottleneck for graceful shutdown/restart operations especially if you have sensitive data to lose and use different resources like files which may be closed properly in that case. 长时间运行的任务可能是正常关闭/重启操作的瓶颈,特别是如果您丢失敏感数据并使用不同的资源,例如在这种情况下可能正确关闭的文件。 Although async. 虽然异步。 methods may not be cancelled immediately whenever you try to cancel the task by using the token, it is still worth giving a try to CancellationToken or similar mechanisms in order to stop/cancel your long running tasks whenever necessary. 当您尝试使用令牌取消任务时,可能无法立即取消方法,仍然值得尝试CancellationToken或类似机制,以便在必要时停止/取消长时间运行的任务。

4) Just adding a new task to your data store is not enough, also you have remove the task whwnever it completed whether successfully or not. 4)仅仅向数据存储添加新任务是不够的,无论何时成功完成任务,您都可以删除任务。 I tried firing an event which signals the completion of task and then I removed this task from the task store. 我尝试触发一个表示任务完成的事件,然后我从任务商店中删除了这个任务。 However, I am not happy with that because firing an event and then binding a method to this event to receive completion of a task is more or less like a continuation in TPL. 但是,我对此并不满意,因为触发事件然后将方法绑定到此事件以接收任务的完成或多或少类似于TPL中的延续。 It's like reinventing the wheel and also events may cause sneaky problems in a multithreaded environment. 这就像重新发明轮子一样,事件也可能在多线程环境中引起偷偷摸摸的问题。

Hope this helps. 希望这可以帮助。 I prefer sharing some experiences instead of posting code, because I do not believe that I have an ultimate solution to this problem. 我更喜欢分享一些经验而不是发布代码,因为我不相信我有这个问题的最终解决方案。 War's response gives a rough idea, but you need to consider many issues before doing that in a production ready system. 战争的反应给出了一个粗略的想法,但在生产就绪系统中,你需要考虑许多问题。

Edit: For long running tasks you may have a look at this thread. 编辑:对于长时间运行的任务,您可以查看线程。 It's a good practice to manage the thread pool. 管理线程池是一种很好的做法。

PushStreamContent may be the answer you're looking for. PushStreamContent可能是您正在寻找的答案。 PushStreamContent helps to stream the response. PushStreamContent有助于传输响应。 Looking at the following blog post to get some idea about the implementation. 查看以下博客文章,了解实施情况。 STREAMING DATA WITH ASP .NET WEB API AND PUSHCONTENTSTREAM 使用ASP .NET WEB API和PUSHCONTENTSTREAM流式传输数据

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM