method - operador await c#
¿Es async/await adecuado para métodos vinculados tanto con IO como con CPU? (3)
Es apropiado await
cualquier operación que sea asincrónica (es decir, esté representada por una Task
).
El punto clave es que para operaciones IO, siempre que sea posible, desea utilizar un método proporcionado que, en esencia, es asincrónico, en lugar de utilizar Task.Run
en un método síncrono de bloqueo. Si está bloqueando un hilo (incluso un hilo del grupo de subprocesos) mientras realiza IO, no está aprovechando la potencia real del modelo de await
.
Una vez que haya creado una Task
que represente su operación, ya no le importa si está unida a la CPU o IO. Para la persona que llama, es solo una operación asíncrona que necesita ser aguardada.
La documentación de MSDN parece indicar que async
y await
son adecuados para tareas vinculadas a IO, mientras que Task.Run
debe usarse para tareas vinculadas a CPU.
Estoy trabajando en una aplicación que realiza solicitudes HTTP para recuperar documentos HTML, que luego analiza. Tengo un método que se ve así:
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
}
¿Es este el uso bueno y adecuado de async
y await
, o lo estoy usando en exceso?
Hay varias cosas a considerar:
- En una aplicación GUI, quiere la menor cantidad de código posible para ejecutar en el hilo de la interfaz de usuario. En ese caso, descargar una operación vinculada a CPU a otro subproceso usando
Task.Run()
es probablemente una buena idea. Aunque los usuarios de tu código pueden hacerlo por sí mismos, si así lo desean. - En algo como la aplicación ASP.NET, no hay ningún hilo de UI y lo único que te importa es el rendimiento. En ese caso, hay una sobrecarga en el uso de
Task.Run()
lugar de ejecutar el código directamente, pero no debería ser significativo si la operación realmente lleva algún tiempo. (Además, hay una sobrecarga al volver al contexto de sincronización, que es una razón más por la que debe usarConfigureAwait(false)
para la mayoría de las personas queawait
en su código de biblioteca). - Si su método es asíncrono (que BTW también debe reflejarse en el nombre del método, no solo su tipo de devolución), las personas esperarán que no bloqueará el hilo de contexto de sincronización, incluso para el trabajo vinculado a la CPU.
Ponderando eso, creo que usar await Task.Run()
es la elección correcta aquí. Tiene algunos gastos generales, pero también algunas ventajas, que pueden ser importantes.
Ya hay dos buenas respuestas, pero para agregar mi 0.02 ...
Si está hablando de consumir operaciones asíncronas, async
/ await
funciona de manera excelente tanto para E / S como para CPU.
Creo que los documentos de MSDN tienen una ligera inclinación hacia la producción de operaciones asíncronas, en cuyo caso desea utilizar TaskCompletionSource
(o similar) para E / S-bound y Task.Run
(o similar) para CPU-bound. Una vez que haya creado el envoltorio de Task
inicial, lo mejor es que consuma async
y lo await
.
Para su ejemplo particular, realmente se reduce a cuánto tiempo llevará LoadHtmlDocument
. Si elimina Task.Run
, lo ejecutará dentro del mismo contexto que llama a LoadPage
(posiblemente en un subproceso de UI). Las pautas de Windows 8 especifican que cualquier operación que requiera más de 50 ms debe realizarse de forma async
... teniendo en cuenta que 50 ms en su máquina de desarrollo pueden ser más largos en la máquina de un cliente ...
Entonces, si puede garantizar que LoadHtmlDocument
se ejecutará por menos de LoadHtmlDocument
ms, puede ejecutarlo directamente:
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
}
Sin embargo, recomendaría ConfigureAwait
como mencionó @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
}
Con ConfigureAwait
, si la solicitud HTTP no se completa de inmediato (de forma sincrónica), esto (en este caso) hará que LoadHtmlDocument
se ejecute en un subproceso de grupo de subprocesos sin una llamada explícita a Task.Run
.
Si está interesado en el rendimiento async
en este nivel, debe consultar el video Stephen Toub y el artículo de MSDN sobre el tema. Él tiene toneladas de información útil.