2014-01-28 3 views
40

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

ответ

50

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

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

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

Если ваш «сервис» является веб службы или что-нибудь еще, что 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 вообще.

+0

Большое спасибо за подробный ответ. Еще одна вещь, которую я хочу уточнить. Когда я запускаю выполнение с помощью 'Task.Run', содержимое запускается в другом потоке, который берется из пула потоков. Тогда нет необходимости вообще блокировать блокирующие вызовы (такие как мой вызов для обслуживания) в 'Run', потому что они всегда будут потреблять по одному потоку каждый, который будет заблокирован во время выполнения метода. В такой ситуации единственное преимущество, которое осталось от «асинхронного ожидания» в моем случае, - это выполнение нескольких действий за раз. Пожалуйста, исправьте меня, если я ошибаюсь. – Eadel

+0

Также, чтобы указать, что «услуга» - это вызов удаленного веб-сервиса, который не связан с ЦП с нашим сервером, но для получения ответа от удаленной машины требуется некоторое время. Что касается второго предположения, мы предоставляем пользователю возможность выполнять некоторую работу во время большинства запросов, но в нашем случае важна скорость и загрузка сервера. Приложение не является внутренним, поэтому существует возможность большого количества запросов с максимальной нагрузкой. – Eadel

+0

Да, единственное преимущество, которое вы получаете от 'Run' (или' Parallel'), - это параллелизм. Операции по-прежнему блокируют поток.Поскольку вы говорите, что услуга является веб-службой, я не рекомендую использовать 'Run' или' Parallel'; вместо этого напишите асинхронный API для этой службы. –

Смежные вопросы