¿Cuándo usaría Task.Yield()?

Resuelto Krumelur asked hace 10 años • 6 respuestas

Estoy usando async/await y Taskmucho, 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?

Krumelur avatar Mar 26 '14 03:03 Krumelur
Aceptado

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.

Reed Copsey avatar Mar 25 '2014 20:03 Reed Copsey

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.Currentes 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.StartNewun programador de tareas adecuado.

Ver también:

  • "await Task.Yield()" y sus alternativas

  • Task.Yield: ¿usos reales?

noseratio avatar Mar 26 '2014 06:03 noseratio

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();
    }
Joakim M. H. avatar Aug 27 '2019 07:08 Joakim M. H.

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 " SynchronizationContextprimero 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?

keithyip avatar Jan 30 '2021 16:01 keithyip