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

时间:2016-08-12 11:25:13

标签: c# .net asp.net-web-api async-await owin

我想在自托管的OWIN环境中的ApiController中运行一个长时间运行的任务(比如说4-5分钟)。但是我想在启动该任务后(在我开始长时间运行的任务时)发回响应,而不等待它完成。这个长时间运行的任务与HTTP无关,并且顺序运行一些可能需要很长时间的方法。

我查看了this博文,并决定尝试QueueBackgroundWorkItem。但是,我不确定是否可以在自托管(控制台应用程序)owin环境中使用此方法或使用它。在我认为的自托管控制台应用程序中,应用程序本身管理请求,并且所有请求都在同一个AppDomain中运行(应用程序默认的AppDomain,我们不创建任何新的appDomain),因此我可以运行一个长时间运行的任务一种昙花一现的时尚,没有做任何特别的事情?

无论如何,当我使用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>

我也在SO上发现了this的问题,说实话,这对我来说有点混乱,因为实现这一目标的唯一方法是IRegisteredObject还是没有?

我只是想在自托管应用程序中运行一个长时间运行的任务,我很欣赏任何有关它的想法。几乎所有的资源,我发现的问题都基于asp.net,我不知道从哪里开始。

5 个答案:

答案 0 :(得分:3)

这几乎是创建Hangfire的目的(https://www.hangfire.io/)。

听起来像是一场不容错过的工作。

fun listToString [] = "[]\n"
|   listToString (c::l) =
        "[" ^ (Int.toString c)
        ^ foldl (fn (s1, s2) => s2 ^ ", " ^ s1) "" (map (Int.toString) l)
        ^ "]\n"
val _  = print (listToString a)

答案 1 :(得分:2)

自托管OWIN应用程序通常是常规控制台应用程序,因此您可以像在控制台应用程序中那样分离一些长时间运行的任务,例如:

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

在IIS托管的ASP.NET应用程序中,不建议使用此类方法,因为应用程序池通常是循环使用的,因此应用程序会经常关闭并重新启动。在这种情况下,您的后台任务将被中止。为了防止(或推迟),已经引入了QueueBackgroundWorkItem之类的API。

但是,由于您是自托管的并且不在IIS中运行,因此您可以使用上面列出的API。

答案 2 :(得分:2)

您需要提供一种机制,用于将任务添加到控制器之外的某些内容。

通常在应用程序中我使用owin作为实际的主机是IIS所以我想出我可以使用&#34;托管进程&#34;但在控制台应用程序中,它可能会简单得多。

最明显的方法是传递某种全局对象/单例,你的DI框架知道可以确定它总是与作业的容器相同的对象&#34;

public class FooController : ApiController
{
    ITaskRunner runner;

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

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

任务运行器可以通过仅仅是一个简单的Task.Run()包装来完成这项工作,但是在那里挂钩并传入它意味着在请求被处理后#34;并且控制器被清理干净,任务仍在考虑范围内#34;所以应用程序可以继续处理它,而不是试图清理它。

答案 3 :(得分:2)

我想对战争的答案发表评论,但回答我自己的问题似乎更好地解释了。抱歉,这可能不是一个完整的答案。

War的解决方案是实现此功能的一种可能方式,我做了类似的事情。基本上,创建任务存储,无论何时触发或完成新任务,都将此任务添加/删除到任务存储。但是,我想提一些问题,因为我认为它并不像那样简单。

1)我同意使用依赖注入框架来实现单例模式。我使用Autofac的'SingleInstance()'方法来做到这一点。但是,正如你在互联网上的许多答案和资源中看到的那样,单身模式并不是流行模式,尽管有时似乎是不可或缺的。

2)您的存储库应该是线程安全的,因此从该任务存储中添加/删除任务应该是线程安全的。您可以使用并发数据结构,如“ConcurrentDictionary”而不是锁定(就cpu时间而言,这是一项昂贵的操作)。

3)您可以考虑使用CancellationToken进行异步。操作。您的应用程序很可能被用户关闭,或者在运行时可能会发生异常。长时间运行的任务可能是正常关闭/重启操作的瓶颈,特别是如果您丢失敏感数据并使用不同的资源,例如在这种情况下可能正确关闭的文件。虽然异步。当您尝试使用令牌取消任务时,可能无法立即取消方法,仍然值得尝试使用CancellationToken或类似机制,以便在必要时停止/取消长时间运行的任务。

4)仅仅向数据存储添加新任务是不够的,无论何时成功完成任务,您都可以删除任务。我尝试触发一个表示任务完成的事件,然后我从任务商店中删除了这个任务。但是,我对此并不满意,因为触发事件然后将方法绑定到此事件以接收任务的完成或多或少类似于TPL中的延续。这就像重新发明轮子一样,事件也可能在多线程环境中造成偷偷摸摸的问题。

希望这会有所帮助。我更喜欢分享一些经验而不是发布代码,因为我不相信我有这个问题的最终解决方案。战争的回应给出了一个粗略的想法,但在生产就绪系统中,你需要考虑许多问题。

编辑:对于长时间运行的任务,您可以查看this线程。管理线程池是一种很好的做法。

答案 4 :(得分:2)

PushStreamContent可能是您正在寻找的答案。 PushStreamContent有助于传输响应。查看以下博客文章,了解实施情况。 STREAMING DATA WITH ASP .NET WEB API AND PUSHCONTENTSTREAM