Подтвердить что ты не робот

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

У меня есть метод в приложении 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);
    });
}

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

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, но это не мой случай. Здесь - хорошая статья, описывающая мой случай, который не описывает ситуацию с параллельными вызовами, отклоняя опцию обертывания вызова синхронизации, но в моем мнение моя ситуация - это именно то, что нужно сделать.
Заранее благодарим за помощь и советы.

4b9b3361

Ответ 1

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

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

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

Если ваш "сервис" - это веб-служба или что-то еще, что связано с I/O-привязкой, лучшим решением будет написать для него асинхронный API.

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

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

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

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

Я приступлю к предположению, что да, вам действительно нужно ускорить выполнение индивидуального запроса.

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

Когда вы используете параллельный код на 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));
}

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