understanding run method example await async c# multithreading asynchronous

example - run method async c#



¿Por qué necesito usar ConfigureAwait(falso) en todos los cierres transitivos? (1)

Estoy aprendiendo async / await y después de leer este artículo No bloquear en el código asíncrono

y esto es asíncrono / espera adecuado para métodos que están vinculados tanto a IO como a CPU

Noté una sugerencia del artículo de @Stephen Cleary.

El uso de ConfigureAwait (falso) para evitar puntos muertos es una práctica peligrosa. Tendría que usar ConfigureAwait (falso) para cada espera en el cierre transitivo de todos los métodos llamados por el código de bloqueo, incluidos todos los códigos de terceros y de terceros. El uso de ConfigureAwait (falso) para evitar el interbloqueo es, en el mejor de los casos, solo un hackeo.

Apareció de nuevo en el código del post como he adjuntado anteriormente.

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 }

Como sé, cuando usemos ConfigureAwait (falso), el resto del método asíncrono se ejecutará en el grupo de subprocesos. ¿Por qué necesitamos agregarlo en cada espera en el cierre transitivo? Yo mismo creo que esta es la versión correcta como lo que sabía.

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()) //IO-bound return LoadHtmlDocument(contentStream); //CPU-bound }

Significa que el segundo uso de ConfigureAwait (falso) en el uso del bloque es inútil. Por favor, dime la forma correcta. Gracias por adelantado.


Como sé, cuando usemos ConfigureAwait(false) el resto del método asíncrono se ejecutará en el grupo de subprocesos.

Cierra, pero hay una advertencia importante que te estás perdiendo. Cuando reanude después de esperar una tarea con ConfigureAwait(false) , reanudará en un hilo arbitrario. Tome nota de las palabras "cuando reanude".

Déjame mostrarte algo:

public async Task<string> GetValueAsync() { return "Cached Value"; } public async Task Example1() { await this.GetValueAsync().ConfigureAwait(false); }

Considere la posibilidad de await en el Example1 . Aunque está esperando un método async , ese método no realiza ningún trabajo asíncrono. Si un método async no await nada, se ejecuta de forma síncrona y el vigilante nunca se reanuda porque, en primer lugar, nunca se suspendió . Como muestra este ejemplo, las llamadas a ConfigureAwait(false) pueden ser superfluas: pueden no tener ningún efecto. En este ejemplo, cualquier contexto en el que estuviera cuando ingresó el Example1 es el contexto en el que estará después de la await .

No es exactamente lo que esperabas, ¿verdad? Y sin embargo, no es del todo inusual. Muchos métodos async pueden contener rutas rápidas que no requieren que el llamante se suspenda. La disponibilidad de un recurso almacenado en caché es un buen ejemplo (gracias, @ jakub-dąbek!), Pero hay muchas otras razones por las que un método async podría rescatarse pronto. A menudo verificamos varias condiciones al comienzo de un método para ver si podemos evitar hacer un trabajo innecesario, y los métodos async no son diferentes.

Veamos otro ejemplo, esta vez desde una aplicación WPF:

async Task DoSomethingBenignAsync() { await Task.Yield(); } Task DoSomethingUnexpectedAsync() { var tcs = new TaskCompletionSource<string>(); Dispatcher.BeginInvoke(Action(() => tcs.SetResult("Done!"))); return tcs.Task; } async Task Example2() { await DoSomethingBenignAsync().ConfigureAwait(false); await DoSomethingUnexpectedAsync(); }

Echa un vistazo a Example2 . El primer método que await siempre se ejecuta de forma asíncrona. Cuando llegamos a la segunda await , sabemos que estamos ejecutando en un subproceso de grupo de subprocesos, por lo que no hay necesidad de ConfigureAwait(false) en la segunda llamada, ¿verdad? Incorrecto. A pesar de tener Async en el nombre y devolver una Task , nuestro segundo método no fue escrito usando async y await . En su lugar, realiza su propia programación y utiliza un TaskCompletionSource para comunicar el resultado. Cuando reanude desde su await , podría [1] terminar ejecutándose en el hilo que haya proporcionado el resultado, que en este caso es el hilo del despachador de WPF. Whoops.

El punto clave aquí es que a menudo no se sabe exactamente lo que hace un método "esperable". Con o sin CongifureAwait , puede terminar corriendo en un lugar inesperado. Esto puede suceder en cualquier nivel de una pila de llamadas async , por lo que la forma más segura de evitar la apropiación involuntaria de un contexto de un solo subproceso es usar ConfigureAwait(false) con cada await , es decir, durante todo el cierre transitivo.

Por supuesto, puede haber ocasiones en las que desee reanudar su contexto actual, y eso está bien. Es ostensiblemente por qué es el comportamiento predeterminado. Pero si realmente no lo necesita, entonces recomiendo usar ConfigureAwait(false) de manera predeterminada. Esto es especialmente cierto para el código de la biblioteca. El código de la biblioteca puede recibir llamadas desde cualquier lugar, por lo que es mejor adherirse al principio de menos sorpresa. Eso significa que no debe bloquear otros subprocesos fuera del contexto de su interlocutor cuando no lo necesite. Incluso si usa ConfigureAwait(false) en cualquier parte del código de su biblioteca, la persona que llama seguirá teniendo la opción de reanudar en su contexto original si eso es lo que quieren.

[1] Este comportamiento puede variar según el marco y la versión del compilador.