¿Qué sucede mientras esperas el resultado de una tarea?

Estoy usando HttpClient para publicar datos en un servicio remoto en un proyecto .NET 4.0. No me preocupa el locking de esta operación, así que pensé que podría omitir ContinueWith o async / await y usar Result.

Mientras estaba depurando, me encontré con un problema donde el servidor remoto no respondía. A medida que avanzaba en el código, parecía que mi código simplemente dejaba de ejecutarse en la tercera línea … la línea puntero de stack actual dejaba de resaltarse en amarillo y no avanzaba a la siguiente línea. Simplemente desapareció. Me tomó un tiempo darme cuenta de que debería esperar a que expirara el pedido.

var client = new HttpClient(); var task = client.PostAsync("http://someservice/", someContent); var response = task.Result; 

Según entendí, al llamar a Result on the Task provocó que el código se ejecutara sincrónicamente, para comportarse más así (sé que no hay un método Post en HttpClient):

 var client = new HttpClient(); var response = client.Post("http://someservice/", someContent); 

No estoy seguro de que esto sea algo malo, solo estoy tratando de entenderlo. ¿Es realmente cierto que, en virtud del hecho de que HttpClient devuelve Tareas en lugar de los resultados directamente, mi aplicación se está aprovechando automáticamente de la asincronía incluso cuando creo que la estoy evitando?

En Windows, todas las E / S son asincrónicas. Las API síncronas son solo una abstracción conveniente.

Por lo tanto, cuando utiliza HttpWebRequest.GetResponse , lo que sucede en realidad es que la E / S se inicia (asincrónicamente) y la HttpWebRequest.GetResponse llamada (sincrónicamente) bloquea, esperando que se complete.

De forma similar, cuando utiliza HttpClient.PostAsync(..).Result , la E / S se inicia (asincrónicamente) y la cadena de llamada (sincrónicamente) bloquea, esperando que se complete.

Normalmente recomiendo a las personas que Task.Result Task.Wait en lugar de Task.Result o Task.Wait por las siguientes razones:

  1. Si bloquea en una Task que es el resultado de un método async , puede entrar fácilmente en una situación de punto muerto .
  2. Task.Result y Task.Wait wrap any exceptions en una AggregateException (porque esas API son remanentes del TPL). Entonces el manejo de errores es más complejo.

Sin embargo, si conoce estas limitaciones, existen situaciones en las que el locking de una Task puede ser útil (por ejemplo, en la Aplicación Main la Consola).

Capturar el resultado de una tarea bloquea el hilo actual. No tiene sentido usar una versión asincrónica de un método en este caso. Post() y PostAsync().Result se bloqueará.

Si desea hacer uso de concurrencia, debe escribirlo como tal:

 async Task PostContent() { var client = new HttpClient(); Task t = await client.PostAsync("http://someservice/", someContent); //code after this line will execute when the PostAsync completes. return t; } 

Como PostContent() sí mismo devuelve una Tarea, el método que la llama también debería esperar.

 async void ProcessResult() { var result = await PostContent(); //Do work with the result when the result is ready } 

Por ejemplo, si llama a ProcessResult() en un controlador de clic de botón, verá que la interfaz de usuario aún responde, otros controles aún funcionan.