Cuando utilice correctamente la tarea.Ejecutar y cuando solo async-esperar


Me gustaría preguntarle su opinión sobre la arquitectura correcta cuando usar Task.Run. Estoy experimentando una interfaz de usuario retrasada en nuestro WPF. NET 4.5 aplicación (con Caliburn Micro framework).

Básicamente estoy haciendo (fragmentos de código muy simplificados):

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // Makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync();

      HideLoadingAnimation();
   }
}

public class ContentLoader
{
    public async Task LoadContentAsync()
    {
        await DoCpuBoundWorkAsync();
        await DoIoBoundWorkAsync();
        await DoCpuBoundWorkAsync();

        // I am not really sure what all I can consider as CPU bound as slowing down the UI
        await DoSomeOtherWorkAsync();
    }
}

De los artículos / videos que leí / vi, sé que await async no se ejecuta necesariamente en un hilo de fondo y para comenzar a trabajar en el fondo necesita envolverlo con await Task.Run(async () => ... ). Usando async await no bloquea la interfaz de usuario, pero todavía se está ejecutando en el subproceso de la interfaz de usuario, por lo que está haciendo que sea rezagado.

¿Dónde está el mejor lugar para poner Tarea.¿Correr?

Debería simplemente

  1. Envuelva la llamada externa porque esto es menos trabajo de enhebrado para. NET

  2. , ¿o debería envolver solo los métodos enlazados a la CPU que se ejecutan internamente con Task.Run, ya que esto lo hace reutilizable para otros lugares? No estoy seguro aquí si comenzar a trabajar en hilos de fondo profundos en el núcleo es una buena idea.

Ad (1), la primera solución sería así:

public async void Handle(SomeMessage message)
{
    ShowLoadingAnimation();
    await Task.Run(async () => await this.contentLoader.LoadContentAsync());
    HideLoadingAnimation();
}

// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.

Ad (2), la segunda solución sería así:

public async Task DoCpuBoundWorkAsync()
{
    await Task.Run(() => {
        // Do lot of work here
    });
}

public async Task DoSomeOtherWorkAsync(
{
    // I am not sure how to handle this methods -
    // probably need to test one by one, if it is slowing down UI
}
Author: Peter Mortensen, 2013-08-02

2 answers

Tenga en cuenta las pautas para realizar el trabajo en un hilo de interfaz de usuario , recopiladas en mi blog:

  • No bloquee el subproceso de interfaz de usuario durante más de 50 ms a la vez.
  • Puede programar ~100 continuaciones en el subproceso de IU por segundo; 1000 es demasiado.

Hay dos técnicas que debes usar:

1) Use ConfigureAwait(false) cuando pueda.

Por ejemplo, await MyAsync().ConfigureAwait(false); en lugar de await MyAsync();.

ConfigureAwait(false) le dice a await que no necesita reanudar en el contexto actual (en este caso, "en el contexto actual" significa "en el subproceso de la interfaz de usuario"). Sin embargo, para el resto de ese método async (después del ConfigureAwait), no puedes hacer nada que suponga que estás en el contexto actual (por ejemplo, actualizar elementos de la interfaz de usuario).

Para obtener más información, consulte mi artículo de MSDN Mejores prácticas en Programación Asíncrona.

2) Use Task.Run para llamar a métodos enlazados a la CPU.

Debes usar Task.Run, pero no dentro de ningún código que quieras ser reutilizable (es decir, código de biblioteca). Así que se usa Task.Run para llamar el método, no como parte de la implementación del método.

Así que el trabajo puramente ligado a la CPU se vería así:

// Documentation: This method is CPU-bound.
void DoWork();

Que llamarías usando Task.Run:

await Task.Run(() => DoWork());

Los métodos que son una mezcla de CPU-bound y I / O-bound deben tener una firma Async con documentación que indique su naturaleza CPU-bound:

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

Que también llamarías usando Task.Run (puesto que es parcialmente CPU-bound):

await Task.Run(() => DoWorkAsync());
 243
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-02 11:17:56

Un problema con su ContentLoader es que internamente opera secuencialmente. Un mejor patrón es paralelizar el trabajo y luego sincronizar al final, por lo que obtenemos

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync(); 

      HideLoadingAnimation();   
   }
}

public class ContentLoader 
{
    public async Task LoadContentAsync()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

Obviamente, esto no funciona si alguna de las tareas requiere datos de otras tareas anteriores, pero debería brindarle un mejor rendimiento general para la mayoría de los escenarios.

 4
Author: Paul Hatcher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-02 11:09:42