Подходит ли async/await для методов, связанных с IO и CPU?
в документации MSDN указано, что async
и await
подходят для задач, связанных с IO, тогда как Task.Run
следует использовать для задач, связанных с CPU.
Я работаю над приложением, которое выполняет HTTP-запросы для получения HTML-документов, которые затем анализирует. У меня есть метод, который выглядит так:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound
}
это хорошее и подходящее использование async
и await
, или я слишком его использую?
3 ответов
уже есть два хороших ответа, но добавить мой 0.02...
если ты про потребления асинхронные операции, async
/await
отлично работает как для ввода-вывода и ЦП.
Я думаю, что документы MSDN имеют небольшой уклон в сторону производства асинхронные операции, в этом случае вы не хотите использовать TaskCompletionSource
(или подобный) для I / O-bound и Task.Run
(или аналогичный) для CPU-bound. Как только вы создадите инициал Task
фантик, лучше потреблено by async
и await
.
для вашего конкретного примера, это действительно сводится к тому, сколько времени LoadHtmlDocument
возьмут. Если убрать Task.Run
, вы будете выполнять его в том же контексте, который вызывает LoadPage
(возможно, в потоке пользовательского интерфейса). В руководящих принципах Windows 8 указано, что любая операция, занимающая более 50 мс, должна быть выполнена async
... имейте в виду, что 50ms на вашем компьютере разработчика может быть больше на клиенте машина...
так что если вы можете гарантировать, что LoadHtmlDocument
будет работать менее 50 мс, вы можете просто выполнить его напрямую:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound
return LoadHtmlDocument(contentStream); //CPU-bound
}
тем не менее, я бы рекомендовал ConfigureAwait
как упоминал @svick:
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var httpResponse = await new HttpClient().GetAsync(address)
.ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync()
.ConfigureAwait(continueOnCapturedContext: false)) //IO-bound
return LoadHtmlDocument(contentStream); //CPU-bound
}
С ConfigureAwait
, если HTTP-запрос не завершается немедленно (синхронно), то это (в данном случае) причиной LoadHtmlDocument
для выполнения в потоке пула потоков без явного вызова Task.Run
.
если вы заинтересованы в async
производительность на этом уровне, вы должны проверить видео и статья MSDN на эту тему. У него куча полезной информации.
есть несколько вещей, чтобы рассмотреть:
- в приложении GUI вы хотите как можно меньше кода для выполнения в потоке пользовательского интерфейса. В этом случае выгрузка операции CPU-bound в другой поток с помощью
Task.Run()
- Это, вероятно, хорошая идея. Хотя пользователи вашего кода могут сделать это сами, если захотят. - в чем-то вроде ASP.NET приложение, нет потока пользовательского интерфейса, и все, что вам нужно, это производительность. В этом случае есть некоторые накладные расходы при использовании
Task.Run()
вместо запуска кода напрямую, но это не должно быть значительным, если операция на самом деле занимает некоторое время. (Кроме того, есть некоторые накладные расходы при возврате к контексту синхронизации, что является еще одной причиной, по которой вы должны использоватьConfigureAwait(false)
для большинстваawait
s в коде библиотеки.) - если ваш метод является асинхронным (который BTW также должен быть отражен в имени метода, а не только в его возвращаемом типе), люди будут ожидать, что он не будет блокировать поток контекста синхронизации, даже для работы с CPU.
утяжелению, что, я думаю, что с помощью await Task.Run()
- это правильный выбор. Он имеет некоторые накладные расходы, но и некоторые преимущества, которые могут быть значительными.
здесь уместно await
любая операция, которая является асинхронной (т. е. представлена Task
).
ключевым моментом является то, что для операций ввода-вывода, когда это возможно, вы хотите использовать предоставленный метод, который является, по сути, асинхронным, а не с помощью Task.Run
о блокирующем синхронном методе. Если вы блокируете поток (даже поток пула потоков) при выполнении ввода-вывода, вы не используете реальную силу await
модель.
как только вы имеете создал Task
это представляет вашу операцию, которую вы больше не заботитесь, если она связана с CPU или IO. Для вызывающего абонента это просто какая-то асинхронная операция, которая должна быть await
-ed.