Обертывания синхронного кода в асинхронный вызов


у меня есть метод в ASP.NET приложение, которое потребляет довольно много времени для завершения. Вызов этого метода может произойти до 3 раз во время одного запроса пользователя, в зависимости от состояния кэша и параметров, которые предоставляет пользователь. Каждый вызов занимает около 1-2 секунд. Сам метод является синхронным вызовом службы и нет возможности переопределить реализацию.
Таким образом, синхронный вызов службы выглядит примерно так следующее:

public OutputModel Calculate(InputModel input)
{
    // do some stuff
    return Service.LongRunningCall(input);
}

и использование метода (обратите внимание, что вызов метода может произойти несколько раз):

private void MakeRequest()
{
    // a lot of other stuff: preparing requests, sending/processing other requests, etc.
    var myOutput = Calculate(myInput);
    // stuff again
}

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

public async Task<OutputModel> CalculateAsync(InputModel input)
{
    return await Task.Run(() =>
    {
        return Calculate(input);
    });
}

использование (часть кода "do other stuff" выполняется одновременно с вызовом службы):

private async Task MakeRequest()
{
    // do some stuff
    var task = CalculateAsync(myInput);
    // do other stuff
    var myOutput = await task;
    // some more stuff
}

мой вопрос заключается в следующем. Я использую правильный подход для ускорения исполнение в ASP.NET приложение или я делаю ненужную работу, пытаясь запустить синхронный код асинхронно? Может ли кто-нибудь объяснить, почему второй подход не является вариантом ASP.NET (если это действительно не так)? Кроме того, если такой подход применим, нужно ли мне вызывать такой метод асинхронно, если это единственный вызов, который мы можем выполнить на данный момент (у меня есть такой случай, когда нет других вещей, которые нужно делать во время ожидания завершения)?
Большинство статей в сети по этой теме описывается использование async-await подход с кодом, который уже предоставляет awaitable методы, но это не мой случай. здесь это хорошая статья, описывающая мой случай, которая не описывает ситуацию параллельных вызовов, отклоняя возможность обернуть вызов синхронизации, но, на мой взгляд, моя ситуация-именно тот случай, чтобы сделать это.
Заранее спасибо за помощь и советы.

1 54

1 ответ:

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

первое, что нужно сделать, это пересмотреть это предположение:

сам метод является синхронным вызовом службы и нет возможности переопределить реализацию.

если ваш "сервис" web сервис или что-нибудь еще, что связано с вводом/выводом, тогда лучшим решением является написание асинхронного API для него.

Я буду исходить из предположения, что ваша "служба" является операцией с привязкой к процессору, которая должна выполняться на той же машине, что и веб-сервер.

если это так, тогда следующее, что нужно оценить, - это еще одно предположение:

мне нужно, чтобы запрос выполнялся быстрее.

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

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

в этом в этом случае вам нужно будет выполнить параллельный код на вашем веб-сервере. Это определенно не рекомендуется вообще, потому что параллельный код будет использовать потоки, которые ASP.NET возможно, потребуется обрабатывать другие запросы, и путем удаления / добавления потоков он будет бросать ASP.NET эвристика threadpool отключена. Таким образом, это решение влияет на весь ваш сервер.

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

Итак, если Вы зашли так далеко, и вы уверены, что хотите сделать параллельную обработку ASP.NET тогда у вас есть несколько вариантов.

один из самых простых методов-использовать Task.Run, очень похоже на ваш существующий код. Однако я не рекомендую реализовывать CalculateAsync метод, так как это означает, что обработка является асинхронной (что это не так). Вместо этого используйте Task.Run в точке вызова:

private async Task MakeRequest()
{
  // do some stuff
  var task = Task.Run(() => Calculate(myInput));
  // do other stuff
  var myOutput = await task;
  // some more stuff
}

кроме того, если он хорошо работает с вашим кодом, вы можете использовать Parallel тип, т. е. Parallel.For,Parallel.ForEach или Parallel.Invoke. Преимущество перед Parallel код заключается в том, что поток запроса используется в качестве одного из параллельных потоков, а затем возобновляет выполнение в контексте потока (там меньше контекста переключение, чем async пример):

private void MakeRequest()
{
  Parallel.Invoke(() => Calculate(myInput1),
      () => Calculate(myInput2),
      () => Calculate(myInput3));
}

Я не рекомендую использовать параллельный LINQ (PLINQ) на ASP.NET вообще.