簡體   English   中英

ApiController中的長時間運行任務(使用WebAPI,自托管OWIN)

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

我想在自托管的OWIN環境中的ApiController中運行一個長時間運行的任務(比如4-5分鍾)。 但是我想在啟動該任務后(在我開始長時間運行的任務時)發回響應,而不等待它完成。 這個長時間運行的任務與HTTP無關,並且順序運行一些可能需要很長時間的方法。

我看一下這篇博客文章並決定嘗試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上發現了這個問題並且說實話它對我來說似乎有點混亂,因為實現它的唯一方法是IRegisteredObject與否?

我只是想在自托管應用程序中運行一個長時間運行的任務,我很欣賞任何有關它的想法。 幾乎所有的資源,我發現的問題都基於asp.net,我不知道從哪里開始。

這幾乎就是Hangfire的創建方式( https://www.hangfire.io/ )。

聽起來像是一場難以忘懷的工作。

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

自托管OWIN應用程序通常是常規控制台應用程序,因此您可以像在控制台應用程序中那樣分離一些長時間運行的任務,例如:

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

在IIS托管的ASP.NET應用程序中,不建議使用此類方法,因為應用程序池通常是循環使用的,因此應用程序會經常關閉並重新啟動。 在這種情況下,您的后台任務將被中止。 為了防止(或推遲),引入了像QueueBackgroundWorkItem這樣的API。

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

您需要提供一種機制,用於將任務添加到控制器之外的某些內容。

通常在應用程序中我使用owin作為實際的主機是IIS,所以我想出我可以使用“托管進程”,但在控制台應用程序中,它可能會更簡單。

想到的最明顯的方法是傳遞某種全局對象/單例,你的DI框架知道可以確定它總是與“工作容器”相同的對象

public class FooController : ApiController
{
    ITaskRunner runner;

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

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

任務運行器只需要一個簡單的Task.Run()包裝就可以完成這項工作,但是在那里掛鈎並傳入它意味着在請求被“處理”並且控制器被清理之后,任務仍被視為“在范圍內”,因此應用程序可以繼續處理它,而不是嘗試清理它。

我想評論戰爭的答案,但回答我自己的問題似乎更好的解釋。 抱歉,這可能不是一個完整的答案。

War的解決方案是實現此功能的一種可能方式,我做了類似的事情。 基本上,創建任務存儲,無論何時觸發或完成新任務,都將此任務添加/刪除到任務存儲。 但是,我想提一些問題,因為我認為它並不像那樣簡單。

1)我同意使用依賴注入框架來實現單例模式。 我使用Autofac的'SingleInstance()'方法來做到這一點。 但是,正如你在互聯網上的許多答案和資源中看到的那樣,單身模式並不是流行的模式,盡管有時似乎是不可或缺的。

2)您的存儲庫應該是線程安全的,因此從該任務存儲添加/刪除任務應該是線程安全的。 您可以使用並發數據結構,如“ConcurrentDictionary”而不是鎖定(就cpu時間而言,這是一項昂貴的操作)。

3)您可以考慮使用CancellationToken進行異步。 操作。 您的應用程序很可能被用戶關閉,或者在運行時可能會發生異常。 長時間運行的任務可能是正常關閉/重啟操作的瓶頸,特別是如果您丟失敏感數據並使用不同的資源,例如在這種情況下可能正確關閉的文件。 雖然異步。 當您嘗試使用令牌取消任務時,可能無法立即取消方法,仍然值得嘗試CancellationToken或類似機制,以便在必要時停止/取消長時間運行的任務。

4)僅僅向數據存儲添加新任務是不夠的,無論何時成功完成任務,您都可以刪除任務。 我嘗試觸發一個表示任務完成的事件,然后我從任務商店中刪除了這個任務。 但是,我對此並不滿意,因為觸發事件然后將方法綁定到此事件以接收任務的完成或多或少類似於TPL中的延續。 這就像重新發明輪子一樣,事件也可能在多線程環境中引起偷偷摸摸的問題。

希望這可以幫助。 我更喜歡分享一些經驗而不是發布代碼,因為我不相信我有這個問題的最終解決方案。 戰爭的反應給出了一個粗略的想法,但在生產就緒系統中,你需要考慮許多問題。

編輯:對於長時間運行的任務,您可以查看線程。 管理線程池是一種很好的做法。

PushStreamContent可能是您正在尋找的答案。 PushStreamContent有助於傳輸響應。 查看以下博客文章,了解實施情況。 使用ASP .NET WEB API和PUSHCONTENTSTREAM流式傳輸數據

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM