Мне нужно реализовать асинхронное действие в моем контроллере для длительного вызова внешнего API.

Посмотрев несколько руководств, я реализовал свой метод следующим образом:

    [AsyncTimeout(200)]
    public async Task<ActionResult> DoAsync()
    {
        // Execute long running call.
        return View();
    }

У меня вопрос: достаточно ли этого, чтобы сделать действительно неблочную асинхронную? Нужно ли мне также применять оператор await, и если да, то как мне это сделать?

6
shenku 9 Мар 2014 в 06:12

4 ответа

Лучший ответ

Мне нужно реализовать асинхронное действие в моем контроллере для длительного вызова внешнего API.
...
У меня вопрос: достаточно ли этого, чтобы сделать действительно неблочную асинхронную? Нужно ли мне также применять оператор await, и если да, то как мне это сделать?

Компилятор C #, вероятно, уже предлагает ключевое слово async здесь избыточным:

[AsyncTimeout(200)]
public async Task<ActionResult> DoAsync()
{
    // Execute long running call.
    return View();
}

Тот факт, что вы добавили ключевое слово async, не заставляет ваш метод волшебным образом работать в фоновом режиме.

Если вы сделаете что-то вроде await Task.Run(() => View()), как предлагает другой ответ, вы все равно не выйдете за границы данный HTTP-запрос. Обработка запроса займет по крайней мере столько же времени, чтобы сгенерировать View, так и без Task.Run. Клиентский браузер по-прежнему будет его ждать.

Этот шаблон хорош для приложения пользовательского интерфейса, когда вам нужно выгрузить работу, связанную с процессором, в поток пула, чтобы избежать блокировки потока пользовательского интерфейса и сохранить отзывчивость пользовательского интерфейса. Однако использование его в обработчике HTTP-запросов внутри приложения ASP.NET почти никогда не является хорошей идеей. Это только повредит производительности и масштабируемости.

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

Долгосрочные фоновые задачи в Asp.Net MVC3.

Однако выполнение длительной фоновой операции по нескольким HTTP-запросам внутри одного и того же серверного процесса ASP.NET не очень хорошая идея. Хотя реализовать этот подход относительно легко, он может создать проблемы с ремонтопригодностью, масштабируемостью и безопасностью IIS.

Возможно, вам будет лучше с отдельной Windows / Служба WCF для этого, которая будет предоставлять API на основе Task. Затем используйте AJAX для периодического опроса службы WCF, используя специальный метод вашего контроллера ASP.NET MVC в качестве прокси для вызова опроса.

8
Community 23 Май 2017 в 15:18

Как написал другой человек, вам нужно асинхронное действие, чтобы ожидать его, чтобы View() асинхронно обернул его в Task.Run

[AsyncTimeout(200)]
public async Task<ActionResult> DoAsync()
{
    // Execute long running call.
    return await Task.Run(() => View());
}

Впоследствии этот метод в основном полезен для вызова из других асинхронных функций или обратных вызовов.

0
Roman A. Taycher 9 Мар 2014 в 08:23
5
Выполнение return await Task.Run(() => View()) в ASP.NET не имеет смысла, это не приложение пользовательского интерфейса. Какова ваша причина этого? Ответ HTTP не будет отправлен клиенту, пока эта задача не будет завершена. Вы все еще используете поток для выполнения задачи. Итак, почему бы просто return View() без async?
 – 
noseratio
9 Мар 2014 в 14:40

Чтобы написать неблокирующий асинхронный код, вам необходимо выполнить какую-то существующую неблокирующую асинхронную операцию, такую ​​как Task.Delay(), или асинхронный сетевой или файловый ввод-вывод.

Короче говоря, ключевое слово await потребляет асинхронность; он его не создает.

Если у вас нет реальной асинхронной работы, await не принесет вам никакой пользы.

9
SLaks 9 Мар 2014 в 06:14
Так как это относится к вызову API, который может занять некоторое время? Будет ли он всегда ждать, пока он завершится?
 – 
shenku
9 Мар 2014 в 09:34
@shenku, механизм ASP.NET действительно будет ждать, прежде чем вернуть View как часть HTTP-запроса. Но браузер на стороне клиента может решить не ждать и тем временем прервать запрос.
 – 
noseratio
10 Мар 2014 в 09:55

У меня вопрос: достаточно ли этого, чтобы сделать действительно неблочную асинхронную? Нужно ли мне также применять оператор await, и если да, то как мне это сделать?

Вы должны использовать ключевое слово await в асинхронных действиях MVC по следующим причинам:

  1. Если ваше действие изменяет состояние вашего приложения (обновляет базу данных и т. Д.), То ключевое слово await дает вам возможность откатить изменения в случае сбоя внешней службы. В противном случае это может привести к несогласованному состоянию, поскольку пользователь не может воспроизвести только данную асинхронную операцию, он может воспроизвести только все действие.
  2. Среда размещения (IIS) может вызывать выгрузку домена приложения по различным причинам.. Когда это произойдет, ваше приложение никогда не сможет получить результат асинхронной операции. В случае обычной обработки запроса ASP.NET ожидает завершения каждой операции. Если выполнение некоторых запросов занимает слишком много времени (и превышает время ожидания завершения работы), ASP.NET прерывает их и отправляет ответ об ошибке.

Вот почему вы не должны использовать ни async без await, ни другие асинхронные методы .NET, такие как TPL. В этом случае предпочтительным решением является настраиваемая служба Windows с WCF, но она значительно усложняет задачи программирования, развертывания и обслуживания.

Конечно, вы можете использовать HostingEnvironment.RegisterObject, вот хорошая статья. Но когда ASP.NET вызывает метод Stop вашей реализации IRegisteredObject интерфейс (во время выключения), у вас есть только 30 секунд (по умолчанию) для сохранения данных. Если у вас есть невыполненные асинхронные операции, вы должны прервать их, пометить их как неудачные и повторить их после перезапуска или отправить пользователям уведомления об ошибках. И если ваше хранилище также будет недоступно в это время, попрощайтесь со своими результатами (включая неудавшиеся результаты).

Вы также можете использовать постоянные очереди, надежную выборку и выделенных рабочих, которые прослушивают эти очереди внутри приложения ASP.NET. Это может защитить вас даже от завершения рабочего процесса. Более того, существует множество проектов, например Resque, Sidekiq, Pyres и т. д., но они предназначены для Другие языки.

Для .NET попробуйте HangFire - он еще находится в стадии разработки, но гораздо более стабильный, чем первоначальная реализация. таких систем и имеет множество различных функций.

0
odinserj 24 Мар 2014 в 02:14