Respuestas:
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
.
Task.Run
para implementarlo, ExecuteFooOnUIThread
se ejecutará en el grupo de subprocesos, no en el subproceso de la interfaz de usuario. Con await 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, si SynchronizationContext.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:
var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
sc.Post(_ => tcs.SetResult(true), null);
else
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;
Task.Yield()
se puede usar como atajo para algunas alteraciones extrañas del flujo de ejecución. Por ejemplo:
async Task DoDialogAsync()
{
var dialog = new Form();
Func<Task> showAsync = async () =>
{
await Task.Yield();
dialog.ShowDialog();
}
var dialogTask = showAsync();
await Task.Yield();
// now we're on the dialog's nested message loop started by dialog.ShowDialog
MessageBox.Show("The dialog is visible, click OK to close");
dialog.Close();
await dialogTask;
// we're back to the main message loop
}
Dicho esto, no puedo pensar en ningún caso en el Task.Yield()
que no se pueda reemplazar con Task.Factory.StartNew
el programador de tareas adecuado.
Ver también:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
no se compilará porque la await showAsync()
expresión no devuelve un Task
(a diferencia de lo que ocurre sin await
). Dicho esto, si lo hace await 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 porque window.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.
private static void Main()
{
RecursiveMethod().Wait();
}
private static async Task RecursiveMethod()
{
await Task.Delay(1);
//await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
await RecursiveMethod();
}
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.
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.