c# .net async-await c#-5.0

c# - deshabilitar la captura de contexto en todo el código de la biblioteca, ConfigureAwait(falso)



.net async-await (2)

Al usar await , de forma predeterminada se captura el SynchronizationContext (si existe) y los bloques de código después de await (bloques de continuación) se ejecutan utilizando ese contexto (lo que da como resultado interruptores de contexto de subprocesos).

public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await DoSomethingElseAsync(); // We are back on the same thread as before here //(well sometimes, depending on how the captured SynchronizationContext is implemented) }

Si bien este valor predeterminado podría tener sentido en el contexto de la interfaz de usuario en la que desea volver a estar en el hilo de la interfaz de usuario una vez completada una operación asincrónica, no parece tener sentido como el predeterminado para la mayoría de los demás escenarios. Ciertamente no tiene sentido para el código interno de la biblioteca, porque

  1. Viene con la sobrecarga de interruptores de contexto de hilos innecesarios.
  2. Es muy fácil producir bloqueos accidentales (como se documenta aquí o aquí ).

Me parece que Microsoft decidió el incumplimiento predeterminado.

Ahora mi pregunta:

¿Hay alguna otra forma (preferiblemente mejor) de resolver esto que saturando todas las llamadas en await en mi código con .ConfigureAwait(false) ? Esto es tan fácil de olvidar y hace que el código sea menos legible.

Actualización: ¿Sería suficiente llamar para await Task.Yield().ConfigureAwait(false); al comienzo de cada método? Si esto me garantizara que estaré en un hilo sin un SynchronizationContext adelante, todas las llamadas en await posteriores no capturarán ningún contexto.


¿Hay una forma mejor de resolver esto que saturando todas las llamadas en espera en mi código con .ConfigureAwait (false)? Esto es tan fácil de olvidar y hace que el código sea menos legible.

Realmente no. No hay ningún interruptor "listo para usar" que pueda activar para cambiar ese comportamiento. Existe la extensión ConfigureAwaiter Checker ReSharper que puede ayudar. La otra alternativa sería tirar tu propio método de extensión personalizado o el contenedor SynchronizationContext que cambia el contexto, o alternativamente, incluso un awaiter personalizado.


Para empezar, await Task.Yield().ConfigureAwait(false) no funcionará porque Yield no devuelve una Task . Hay otras maneras de saltar a un hilo de la agrupación, pero tampoco se recomienda su uso, verifique "¿Por qué se eliminó" SwitchTo "de Async CTP / Release?"

Si todavía quieres hacerlo, aquí tienes un truco interesante explotando el hecho de que ConfigureAwait(false) empuja la continuación a un hilo de grupo si hay un contexto de sincronización en el hilo original, aunque aquí no hay asincronía:

static Task SwitchAsync() { if (SynchronizationContext.Current == null) return Task.FromResult(false); // optimize var tcs = new TaskCompletionSource<bool>(); Func<Task> yield = async () => await tcs.Task.ConfigureAwait(false); var task = yield(); tcs.SetResult(false); return task; } // ... public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await SwitchAsync().ConfigureAwait(false); // We''re on a thread pool thread without a SynchronizationContext await DoSomethingElseAsync(); // no need for ConfigureAwait(false) here // ... }

Nuevamente, esto no es algo que usaría extensivamente. He tenido preocupaciones similares sobre el uso de ConfigureAwait(false) . Un resultado fue que, aunque ConfigureAwait(false) podría no ser universalmente perfecto, usarlo con la await tan pronto como no te importe el contexto de sincronización es el camino a seguir. Esta es la guía que sigue el código fuente de .NET.

Otro resultado fue que, si le preocupa un código de terceros dentro de DoSomethingElseAsync que puede no estar utilizando ConfigureAwait(false) correctamente, solo haga lo siguiente:

public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await Task.Run(() => DoSomethingElseAsync()).ConfigureAwait(false); // We''re on a thread pool thread without a SynchronizationContext await DoYetSomethingElseAsync(); // no need for ConfigureAwait(false) here // ... }

Esto utilizará la Task.Run la Task.Run que acepta un Func<Task> lambda, ejecutarlo en un hilo de grupo y devolver una tarea desenvuelta. Lo harías solo por primera vez dentro de DoSomethingAsync . El costo potencial es el mismo que para SwitchAsync : un cambio de hilo adicional, pero el código es más legible y mejor estructurado. Este es el enfoque que uso en mi trabajo.