Web Api - Пожар и Забыть

У меня есть действие веб-API, где мне нужно запустить какую-то задачу и забыть об этой задаче. Так организован мой метод:

public async Task<SomeType> DoSth()
{
    await Task.Run(...);
    .....
    //Do some other work
}

Дело в том, что, очевидно, он останавливается на ожидающей линии, ожидая, когда это будет сделано, и только затем продолжит работу. И мне нужно "стрелять и забывать", Должен ли я просто вызвать Task.Run() без ожидания async?

Ответ 1

И мне нужно "стрелять и забывать"

У меня есть сообщение в блоге, в котором подробно описаны несколько разных подходов для fire-and-forget на ASP.NET.

Вкратце: во-первых, постарайтесь не делать огонь и забыть вообще. Это почти всегда плохая идея. Вы действительно хотите "забыть"? Как и в случае, не важно, успешно ли оно завершено или нет? Игнорировать любые ошибки? Принять случайную "потерянную работу" без каких-либо уведомлений о регистрации? Почти всегда, ответ - нет, огонь-и-забыть не является подходящим подходом.

Надежным решением является построение надлежащей распределенной архитектуры. То есть, создайте сообщение, которое представляет выполняемую работу, и оставьте это сообщение в надежной очереди (например, Azure Queue, MSMQ и т.д.). Затем создайте независимую бэкэнд, которая обрабатывает эту очередь (например, Azure WebJob, Win32-сервис и т.д.).

Должен ли я просто вызвать Task.Run() без асинхронного ожидания?

Нет. Это худшее возможное решение. Если вы должны сделать огонь и забыть, и вы не хотите создавать распределенную архитектуру, тогда рассмотрите Hangfire. Если это не сработает для вас, то, по крайней мере, вы должны зарегистрировать свою работу с ковбойским фоном со временем выполнения ASP.NET через HostingEnvironment.QueueBackgroundWorkItem или мой ASP.NET Background Tasks library. Обратите внимание, что QBWI и AspNetBackgroundTasks являются ненадежными решениями; они просто сводят к минимуму вероятность того, что вы потеряете работу, а не предотвратить ее.

Ответ 2

В asp.net могут быть трудными задачи с огнем и забытьем, поскольку они могут часто умирать вместе с запросом, что они были созданы как часть.

Если вы используете 4.5.2+, вы можете использовать QueueBackgroundWorkItem для запуска задачи. Регистрируя задачи с помощью этого метода, AppDomain будет попробовать, чтобы задержать завершение работы, пока все они не будут завершены, но все же могут быть случаи, когда они будут убиты до их завершения. Это, наверное, самая простая вещь, но стоит прочитать, чтобы увидеть, какие экземпляры могут привести к отмене заданий.

HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
  await Task.Run(...);
});

Существует инструмент hangfire, который использует постоянное хранилище, чтобы гарантировать, что задача завершена, и имеет встроенную повторную попытку и ошибку запись. Это больше подходит для "фоновых задач", но подходит для огня и забывает. Это относительно легко настроить и предлагает множество резервных хранилищ, я не могу вспомнить точные данные, но некоторые требуют лицензии, а некоторые нет (например, MSSQL).

Ответ 3

Никогда не стреляйте и не забывайте, потому что тогда вам не удастся увидеть какие-либо ошибки, что приведет к очень неудобному устранению неполадок, если что-то пойдет не так (при использовании метода задачи его обработка исключений не гарантируется, потому что задача не может успешно начаться в первую очередь). Если вы действительно не возражаете, если задача что-то делает или нет, но это довольно необычно (поскольку, если вам действительно все равно, зачем запускать задачу в первую очередь)? По крайней мере, создайте свою задачу с продолжением:

Task.Run(...)
  .ContinueWith(t => 
    logException(t.Exception.GetBaseException()),
    TaskContinuationOptions.OnlyOnFaulted
  )
;

Вы можете сделать это более сложным, как того требуют ваши потребности.

В конкретном случае веб-API вам может потребоваться дождаться завершения фоновых задач до завершения вашего запроса. Если вы этого не сделаете, вы оставите все, что работает в фоновом режиме, что может исказить, сколько нагрузки ваша служба действительно может предпринять, или даже вообще перестать работать, если клиенты запускают слишком много запросов, и вы ничего не делаете для их дросселирования. Вы можете собрать задачи и выпустить await Task.WhenAll(...) в конце для достижения этого; таким образом, вы можете продолжать делать полезную работу, пока ваши фоновые задачи уходят, но вы не вернетесь, пока все не закончится.

Ответ 4

Чтобы стрелять и забыть, используйте этот

Task.Factory.StartNew(async () =>
{
    using (HttpClient client = new HttpClient())
    {
        await client.PostAsync("http://localhost/api/action", new StringContent(""));
    }
});

Ответ 5

Я использую HangFire.

Это лучше для меня.

Простой способ выполнения фоновой обработки в .NET и .NET Core приложениях. Нет необходимости в службе Windows или отдельном процессе.

Поддерживается постоянным хранилищем. Открытые и бесплатные для коммерческого использования.

Ответ 6

Я согласен с другими, что вы не должны просто забывать о своем звонке. Тем не менее, чтобы ответить на ваш вопрос, если вы удалите ожидание из строки Task.Run(), вызов не будет блокироваться, как показано здесь

public async Task<SomeType> DoSth()
{
    Task.Run(...);
    .....
    //Do some other work while Task.Run() continues in parallel.
}