Cuando usa async
/ await
, no hay garantía de que el método al que llama cuando lo haga await FooAsync()
realmente se ejecute de forma asincrónica. La implementación interna es libre de regresar utilizando una ruta completamente sincrónica.
Si está haciendo una API donde es crítico que no bloquee y ejecute algún código de forma asincrónica, y existe la posibilidad de que el método llamado se ejecute sincrónicamente (bloqueo efectivo), el uso await Task.Yield()
obligará a su método a ser asíncrono y devolverá control en ese punto. El resto del código se ejecutará más adelante (en ese momento, aún puede ejecutarse sincrónicamente) en el contexto actual.
Esto también puede ser útil si realiza un método asincrónico que requiere una inicialización de "ejecución prolongada", es decir:
private async void button_Click(object sender, EventArgs e)
{
await Task.Yield(); // Make us async right away
var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later
await UseDataAsync(data);
}
Sin la Task.Yield()
llamada, el método se ejecutará sincrónicamente hasta la primera llamada a await
.
await Task.Yield()
obliga al método a ser asíncrono, ¿por qué nos molestaría escribir código asincrónico "real"? Imagine un método de sincronización pesado. Para hacerlo asíncrono, simplemente agregueasync
yawait Task.Yield()
al principio y mágicamente, ¿será asíncrono? Eso sería más o menos como envolver todo el código de sincronizaciónTask.Run()
y crear un método asíncrono falso.Task.Run
para implementarlo,ExecuteFooOnUIThread
se ejecutará en el grupo de subprocesos, no en el subproceso de la interfaz de usuario. Conawait Task.Yield()
, lo fuerza a ser asíncrono de manera que el código posterior todavía se ejecute en el contexto actual (solo en un momento posterior). No es algo que normalmente haría, pero es bueno que exista la opción si es necesario por alguna extraña razón.ExecuteFooOnUIThread()
durara mucho tiempo, aún bloquearía el hilo de la interfaz de usuario durante un tiempo prolongado en algún momento y haría que la interfaz de usuario no respondiera, ¿es correcto?Internamente,
await Task.Yield()
simplemente pone en cola la continuación en el contexto de sincronización actual o en un subproceso de grupo aleatorio, siSynchronizationContext.Current
es asínull
.Se implementa eficientemente como camarero personalizado. Un código menos eficiente que produzca el mismo efecto podría ser tan simple como esto:
Task.Yield()
se puede usar como atajo para algunas alteraciones extrañas del flujo de ejecución. Por ejemplo:Dicho esto, no puedo pensar en ningún caso en el
Task.Yield()
que no se pueda reemplazar conTask.Factory.StartNew
el programador de tareas adecuado.Ver también:
"aguardar Task.Yield ()" y sus alternativas
Tarea.Rendimiento: ¿usos reales?
fuente
var dialogTask = await showAsync();
?var dialogTask = await showAsync()
no se compilará porque laawait showAsync()
expresión no devuelve unTask
(a diferencia de lo que ocurre sinawait
). Dicho esto, si lo haceawait showAsync()
, la ejecución después se reanudará solo después de que se haya cerrado el diálogo, así es como es diferente. Eso es porquewindow.ShowDialog
es una API síncrona (a pesar de que todavía bombea mensajes). En ese código, quería continuar mientras todavía se muestra el diálogo.Un uso de
Task.Yield()
es evitar un desbordamiento de la pila al hacer una recursión asíncrona.Task.Yield()
evita la continuación sincrónica. Sin embargo, tenga en cuenta que esto puede causar una excepción OutOfMemory (como lo señaló Triynko). La recursión sin fin todavía no es segura y probablemente sea mejor reescribir la recursión como un bucle.fuente
await Task.Delay(1)
es suficiente para prevenirlo. (Aplicación de consola, .NET Core 3.1, C # 8)Task.Yield()
puede usarse en implementaciones simuladas de métodos asincrónicos.fuente