Почему я должен создавать операции async WebAPI вместо операций синхронизации?


109

У меня есть следующая операция в созданном мной веб-API:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

Вызов этого веб-сервиса выполняется через вызов JQuery Ajax следующим образом:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

Я видел, как некоторые разработчики реализуют предыдущую операцию таким образом:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

Однако следует сказать, что GetProductsWithHistory () - довольно долгая операция. Какую пользу мне принесет асинхронная работа webAPI с учетом моей проблемы и контекста?


1
На стороне клиента используется AJAX, который уже является асинхронным. Вам не нужно, чтобы служба также была написана как async Task<T>. Помните, AJAX был реализован еще до того, как TPL вообще существовала :)
Доминик Зукевич

65
Вы должны понимать, почему вы реализуете асинхронные контроллеры, многие этого не делают. IIS имеет ограниченное количество доступных потоков, и когда все они используются, сервер не может обрабатывать новые запросы. С помощью асинхронных контроллеров, когда процесс ожидает завершения ввода-вывода, его поток освобождается для использования сервером для обработки других запросов.
Матия Грчич

3
Какие разработчики делали это? Если есть какой-либо пост в блоге или статья, в которой рекомендуется этот метод, разместите ссылку.
Стивен Клири

3
Вы получаете все преимущества асинхронности только в том случае, если ваш процесс поддерживает асинхронность сверху (включая само веб-приложение и ваши контроллеры) до любых ожидаемых действий, выходящих за пределы вашего процесса (включая задержки таймера, файловый ввод-вывод, доступ к БД, и веб-запросы, которые он делает). В этом случае ваш помощник делегата нуждается в GetProductsWithHistoryAsync()возврате Task<CartTotalsDTO>. Написание асинхронного режима вашего контроллера может оказаться полезным, если вы намереваетесь также перенести выполняемые им вызовы в асинхронные; тогда вы начнете получать выгоду от асинхронных частей по мере переноса остальных.
Кейт Робертсон

1
Если процесс, который вы делаете, запускается и попадает в базу данных, тогда ваш веб-поток просто ожидает его возврата и удерживает этот поток. Если вы достигли максимального количества потоков и поступил другой запрос, он должен подождать. Зачем это делать? Вместо этого вы захотите освободить этот поток от вашего контроллера, чтобы другой запрос мог его использовать и использовать другой веб-поток только тогда, когда ваш исходный запрос из базы данных вернулся. msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521 06

Ответы:


98

В вашем конкретном примере операция не является асинхронной, поэтому вы делаете асинхронную синхронизацию. Вы просто освобождаете один поток и блокируете другой. Для этого нет причин, потому что все потоки являются потоками пула потоков (в отличие от приложения с графическим интерфейсом).

В моем обсуждении «асинхронности вместо синхронизации» я настоятельно рекомендовал, чтобы если у вас есть API, который внутренне реализуется синхронно, вы не должны предоставлять асинхронный аналог, который просто обертывает синхронный метод Task.Run.

От Должен ли я предоставлять синхронные оболочки для асинхронных методов?

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


3
@efaruk все потоки являются рабочими потоками. Освобождение одного потока ThreadPool и блокирование другого бессмысленно.
i3arnon 06

1
@efaruk Я не уверен, что вы пытаетесь сказать ... но если вы согласны, нет причин использовать асинхронность вместо синхронизации в WebAPI, тогда все в порядке.
i3arnon

@efaruk "async over sync" (т.е. await Task.Run(() => CPUIntensive())) бесполезен в asp.net. Вы ничего не получите от этого. Вы просто освобождаете один поток ThreadPool, чтобы он занимал другой. Это менее эффективно, чем простой вызов синхронного метода.
i3arnon

1
@efaruk Нет, это неразумно. Ваш пример запускает независимые задачи последовательно. Вам действительно нужно прочитать asyc / await, прежде чем давать рекомендации. Вам нужно будет использовать await Task.WhenAllдля параллельного выполнения.
Søren Boisen

1
@efaruk Как объясняет Бойзен, ваш пример не добавляет никакой ценности помимо простого последовательного вызова этих синхронных методов. Вы можете использовать, Task.Runесли хотите распараллелить нагрузку на несколько потоков, но это не то, что означает «асинхронность поверх синхронизации». "async over sync" - ссылки на создание асинхронного метода в качестве оболочки над синхронным. Вы можете увидеть в цитате моего ответа.
i3arnon

1

Одним из подходов может быть (я успешно использовал это в клиентских приложениях), чтобы служба Windows выполняла длительные операции с рабочими потоками, а затем делала это в IIS, чтобы освободить потоки до тех пор, пока операция блокировки не будет завершена: Обратите внимание, это предполагает результаты сохраняются в таблице (строки, идентифицируемые с помощью jobId), и более чистый процесс очищает их через несколько часов после использования.

Чтобы ответить на вопрос: «Учитывая мою проблему и контекст, какую пользу мне принесет асинхронная работа webAPI?» учитывая, что это «довольно долгая операция», я думаю, много секунд, а не мс, этот подход освобождает потоки IIS. Очевидно, вам также необходимо запустить службу Windows, которая сама потребляет ресурсы, но этот подход может предотвратить поток медленных запросов от кражи потоков из других частей системы.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.