¿Cuándo usaría Task.Yield()?
Estoy usando async/await y Task
mucho, pero nunca he usado Task.Yield() y, para ser honesto, incluso con todas las explicaciones, no entiendo por qué necesitaría este método.
¿ Alguien puede dar un buen ejemplo de dónde Yield()
se requiere?
Cuando usa async
/ await
, no hay garantía de que el método que llame cuando lo haga await FooAsync()
realmente se ejecute de forma asincrónica. La implementación interna puede regresar libremente utilizando una ruta completamente sincrónica.
Si está creando una API en la que es fundamental no bloquear y ejecutar algún código de forma asincrónica, y existe la posibilidad de que el método llamado se ejecute de forma sincrónica (bloqueando efectivamente), el uso await Task.Yield()
forzará que su método sea asincrónico y regrese controlar en ese punto. El resto del código se ejecutará más adelante (en cuyo momento, aún puede ejecutarse sincrónicamente) en el contexto actual.
Esto también puede ser útil si crea un método asincrónico que requiere una inicialización de "larga duración", 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
.
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 null
.
Se implementa eficientemente como camarero personalizado. Un código menos eficiente que produzca el mismo efecto podría ser tan simple como este:
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 utilizar 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 se me ocurre ningún caso en el que Task.Yield()
no se pueda reemplazar con Task.Factory.StartNew
un programador de tareas adecuado.
Ver también:
"await Task.Yield()" y sus alternativas
Task.Yield: ¿usos reales?
Un uso de Task.Yield()
es evitar un desbordamiento de la pila al realizar una recursividad asíncrona. Task.Yield()
impide la continuación sincrónica. Sin embargo, tenga en cuenta que esto puede provocar una excepción OutOfMemory (como lo señaló Triynko). La recursividad infinita todavía no es segura y probablemente sea mejor reescribir la recursividad 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();
}
Task.Yield()
es como una contraparte de Thread.Yield()
async-await pero con condiciones mucho más específicas. ¿Cuántas veces necesitas Thread.Yield()
? Task.Yield()
Primero responderé al título "¿cuándo usarías " de manera amplia? Lo haría cuando se cumplan las siguientes condiciones:
- desea devolver el control al contexto asíncrono (sugiriendo al programador de tareas que ejecute primero otras tareas en la cola)
- Es necesario continuar en el contexto asíncrono.
- Prefiero continuar inmediatamente cuando el programador de tareas esté libre.
- no quiero ser cancelado
- Prefiero código más corto
El término "contexto asíncrono" aquí significa " SynchronizationContext
primero y luego TaskScheduler
". Fue utilizado por Stephen Cleary .
Task.Yield()
es aproximadamente hacer esto (muchas publicaciones se equivocan un poco aquí y allá):
await Task.Factory.StartNew(
() => {},
CancellationToken.None,
TaskCreationOptions.PreferFairness,
SynchronizationContext.Current != null?
TaskScheduler.FromCurrentSynchronizationContext():
TaskScheduler.Current);
Si alguna de las condiciones no se cumple, es necesario utilizar otras alternativas.
Si la continuación de una tarea debe estar en Task.DefaultScheduler
, normalmente se utiliza ConfigureAwait(false)
. Al contrario, Task.Yield()
te da un esperado no tener ConfigureAwait(bool)
. Debe utilizar el código aproximado con TaskScheduler.Default
.
Si Task.Yield()
obstruye la cola, debe reestructurar su código como lo explica noseratio .
Si necesita que la continuación ocurra mucho más tarde, digamos, en el orden de milisegundos, usaría Task.Delay
.
Si desea que la tarea se pueda cancelar en la cola pero no desea verificar el token de cancelación ni generar una excepción usted mismo, debe usar el código aproximado con un token de cancelación.
Task.Yield()
es tan específico y fácil de esquivar. Solo tengo un ejemplo imaginario mezclando mi experiencia. Es para resolver un problema de filósofo de cena asíncrono limitado por un programador personalizado . En mi biblioteca auxiliar de subprocesos múltiples InSync , admite adquisiciones desordenadas de bloqueos asíncronos. Pone en cola una adquisición asíncrona si la actual falló. El código está aquí . Se necesita ConfigureAwait(false)
como biblioteca de uso general, por lo que necesito usar Task.Factory.StartNew
. En un proyecto de código cerrado, mi programa necesita ejecutar un código sincrónico significativo mezclado con código asíncrono con
- una alta prioridad de subproceso para trabajo en tiempo semi-real
- una prioridad de hilo baja para algunos trabajos en segundo plano
- una prioridad de hilo normal para la interfaz de usuario
Por lo tanto, necesito un programador personalizado . Podría imaginar fácilmente que algunos desarrolladores pobres de alguna manera necesitan mezclar código sincronizado y asíncrono junto con algunos programadores especiales en un universo paralelo (un universo probablemente no contenga tales desarrolladores); pero ¿por qué no utilizarían simplemente el código aproximado más robusto para no tener que escribir un comentario extenso para explicar por qué y qué hace?