MVC4 + async/wait + return ответ до завершения действия

В моем приложении MVC4 мне нужно добавить контроллер для загрузки и обработки больших файлов. Сразу после загрузки файла мне нужно запустить асинхронную обработку этого файла и вернуть ответ браузеру, не дожидаясь завершения обработки.

Очевидно, я мог бы начать новый поток для обработки файла вручную, но мне интересно, могу ли я реализовать этот сценарий с помощью механизма async/wait, представленного с .net 4.5

Чтобы проверить концепцию, я пробовал что-то вроде этого:

public async Task<ActionResult> Test()
{
    TestAsync();
    return View("Test");
}

public async void TestAsync()
{
    await LongRunning();
}

private Task<int> LongRunning()
{
    return Task<int>.Factory.StartNew(() => Pause());
}

private int Pause()
{
    Thread.Sleep(10000);
    return 3;
}

Механизм async, похоже, работает в целом: когда я отлаживаю код, я нажимаю "Return View" ( "Тест" ); перед линией "return 3". Однако браузер получает ответ только после завершения метода Pause.

Это похоже на регулярные асинхронные контроллеры (те, которые имеют методы Async и Completed). Есть ли способ использовать async/wait в контроллерах для моего сценария?

Ответы

Ответ 1

Очевидно, я мог бы начать новый поток для обработки файла вручную, но мне интересно, могу ли я реализовать этот сценарий с помощью механизма async/wait, представленного с .net 4.5

Нет, вы не можете, потому что async не изменяет протокол HTTP.

Свик и Джеймс уже опубликовали правильные ответы в качестве комментариев, которые я дублирую ниже для удобства:

IIS может в любое время переработать ваше приложение.

Если у вас есть длительные действия для асинхронизации с запросом, выполните их в другом месте. "Обычным" является постоянная очередь (MSMQ, Azure, RabbitMQ и т.д.) С чем-то другим (служба Windows, exe, выполняемая планировщиком задач, приложение с использованием Quartz.net и т.д.), Обрабатывая их.

Подводя итог, HTTP дает вам один запрос и один ответ (async - и все остальное - не изменит это).

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

В вашей ситуации я рекомендую вам следовать предложению Джеймса:

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

Есть несколько вариантов этого (например, использование SignalR для уведомления обозревателя при завершении обработки), но общая архитектура одинаков.

Это сложно, но это правильный способ сделать это.

Ответ 2

У вас есть ошибка в коде. Вы должны дождаться вызова TestAsync в своем действии. Если вы этого не сделаете, он просто возвращает объект "Задача", не запуская ничего.

public async Task<ActionResult> Test()
{
    await TestAsync();
    return View("Test");
}

и правильный способ спать в асинхронном методе - вызвать

await Task.Delay(10000);

Вы должны изменить подпись TestAsync: вы не должны отмечать метод async, если он возвращает void. Вместо этого он должен вернуть задачу. Возвращаемая пустота зарезервирована для совместимости с событиями .net.

public async Task TestAsync()
{
    await LongRunning();
}

Тело метода совпадает.

Ответ 3

Ваш метод LongRunning синхронно спадает 10 секунд. Измените его, чтобы вместо этого выполнялся спящий режим.