2016-08-12 2 views
5

Я хотел бы запустить многолетнюю задачу (скажем, 4-5 минут) в ApiController в собственной среде OWIN. Однако я хотел бы отправить ответ после запуска этой задачи (как только я запустил долговременную задачу), не дождавшись завершения. Эта долговременная задача не имеет ничего общего с HTTP и последовательно запускает некоторые методы, которые могут занять очень много времени.Долгосрочная задача в ApiController (с использованием WebAPI, самообслуживаемого OWIN)

Я смотрю сообщение в блоге 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> 

Также я нашел this вопрос о SO и, честно говоря, кажется немного запутанным для меня, как единственный способ достижения этой цели IRegisteredObject или нет?

Я просто пытаюсь запустить многолетнюю задачу в самостоятельном приложении, и я ценю любые идеи об этом. Почти все ресурсы, которые я нашел, основаны на asp .net, и я не уверен, с чего начать.

+0

Не стоит ли ставить в очередь задачу (в базе данных) и разрешить какой-либо сервис ее обрабатывать? –

+0

Hangfire (https://www.hangfire.io/) делает что-то похожее, насколько я знаю.Тем не менее, я считаю, что это немного отличается от вопроса. – Deniz

ответ

2

резидентных приложений Owin, как правило, обычные консольные приложения, поэтому вы можете закрутить некоторые давно запущенной задачи таким же образом, вы могли бы сделать, что в консольном приложении, например:

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

в IIS размещенных приложений ASP.NET не рекомендуется использовать такие методы потому что пулы приложений обычно перерабатываются, поэтому ваше приложение закрывается и перезапускается довольно часто. В таком случае фоновая задача будет прервана. Чтобы предотвратить (или отложить), были введены API, такие как QueueBackgroundWorkItem.

Однако, поскольку вы сами размещены и не работаете в IIS, вы можете просто использовать перечисленные выше API.

2

Вам необходимо предоставить механизм для добавления задачи к чему-либо вне контроллера.

Как правило, в приложениях, использующих owin для реального хоста, есть IIS, поэтому я понял, что могу использовать «Hosted Processes», но в консольном приложении это будет намного проще.

Наиболее очевидный подход, который приходит на ум, чтобы пройти в какой-то глобальный объект/одноплодной, что ваша база DI знает, может быть уверен, что всегда тот же объект, как «контейнер для работы»

public class FooController : ApiController 
{ 
    ITaskRunner runner; 

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

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

Бегун задачи мог бы выполнить эту задачу, будучи немного более простой оболочкой вокруг Задачи.Выполнить(), если хотите, но наличие этого крючка там и передано означает, что после того, как запрос «обработан» и контроллер будет очищен, задача по-прежнему считается «в области», поэтому приложение может продолжать обрабатывать ее вместо пытаясь очистить его.

2

Я хотел прокомментировать ответ Войны, но, отвечая на мой собственный вопрос, кажется более длинным объяснением. Это может быть не полный ответ, извините.

Решение войны - это возможный способ реализации этой функции, и я сделал что-то похожее на это. В принципе, создайте хранилище задач и всякий раз, когда новая задача запускается или завершается, добавьте/удалите эту задачу в хранилище задач. Однако я хотел бы упомянуть некоторые проблемы, потому что считаю, что это не так просто, как это.

1) Я согласен с использованием инфраструктуры инъекций зависимостей для реализации одноэлементного шаблона. Для этого я использовал метод SingleInstance() для Autofac. Однако, как вы можете видеть во многих ответах и ​​ресурсах в Интернете, одноэлементный шаблон не является популярным, хотя иногда кажется необходимым.

2) Ваш репозиторий должен быть потокобезопасным, поэтому добавление/удаление задач из этого хранилища задач должно быть потокобезопасным. Вы можете использовать параллельную структуру данных, например «ConcurrentDictionary», вместо блокировки (что является дорогостоящей операцией с точки зрения времени процессора).

3) Вы можете использовать CancellationToken для своего асинхронного использования. операции. Вполне возможно, что ваше приложение может быть отключено пользователем или исключение, которое может быть обработано, может произойти во время выполнения. Долгосрочные задачи могут быть узким местом для изящных операций shutdown/restart, особенно если у вас есть конфиденциальные данные, чтобы потерять и использовать разные ресурсы, такие как файлы, которые могут быть закрыты должным образом в этом случае. Хотя асинхронный. методы не могут быть отменены немедленно, когда вы пытаетесь отменить задачу, используя токен, все равно стоит попробовать CancellationToken или аналогичные механизмы, чтобы остановить/отменить ваши длительные задачи, когда это необходимо.

4) Просто добавить новую задачу в хранилище данных недостаточно, также вы удалили задачу, если она завершилась успешно, или нет. Я попытался запустить событие, которое сигнализирует о завершении задачи, а затем я удалил эту задачу из хранилища задач. Тем не менее, я не доволен этим, потому что увольнение события, а затем привязка метода к этому событию для получения завершения задачи более или менее похоже на продолжение в TPL. Это похоже на переосмысление колеса, а также события могут вызвать скрытые проблемы в многопоточной среде.

Надеюсь, это поможет. Я предпочитаю обмен опытом, а не публикацию кода, потому что я не считаю, что у меня есть окончательное решение этой проблемы. Ответ на войну дает приблизительную идею, но перед тем, как это сделать в готовой к производству системе, вам нужно рассмотреть многие проблемы.

Редактировать: Для выполнения длительных задач вы можете взглянуть на нить this. Это хорошая практика для управления пулом потоков.

+0

Ей мои мысли точно ... Я старался не пустить мой ответ со слишком большой фоновой информацией, которая может или не может делать предположения о вашей реализации. Для полного решения вы хотели бы избежать делать что-то совершенно такое, что просто в пользу соответствия стандартам шаблонов/кодирования в resto f вашей кодовой базе. Что касается проблем с потоками, то ваш уровень данных/бизнес-логика должен справиться с этим, поэтому на этом уровне я не буду беспокоиться о том, чтобы начать работу с потоками/задачами. Мой 2p ... рад, что вы его решили, хотя :) – War

3

Это в значительной степени то, что создано Hangfire (https://www.hangfire.io/).

Похоже на работу с огнем и забытьем.

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

PushStreamContent может быть ответом, который вы ищите. PushStreamContent помогает передавать ответ. Глядя на следующее сообщение в блоге, чтобы получить представление о реализации. STREAMING DATA WITH ASP .NET WEB API AND PUSHCONTENTSTREAM

Смежные вопросы