442 votes

Quand utiliser correctement la Tâche.Exécuter et lorsque la juste asynchrone attendent

Je voudrais vous demander votre avis à propos de l'architecture correcte lors de l'utilisation de la Tâche.Exécuter. Je suis en train de vivre lag de l'INTERFACE utilisateur dans notre WPF .net 4.5 application. (avec Caliburn Micro framework).

Ce que je suis essentiellement faire est (très simplifié des extraits de code)

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

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();
      await this.contentLoader.LoadContentAsync();  // makes UI very laggy, but still not dead
      HideLoadingAnimation();   
   }
}

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

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

Les articles/vidéos que j'ai lu/vu que je connais qui attendent asynchrone n'est pas nécessairement en cours d'exécution sur bg fil et commencer à travailler en arrière-plan, vous devez envelopper avec attendent Task.Run(async () => ... ). À l'aide de async attendent de ne pas bloquer l'INTERFACE utilisateur, mais est toujours en cours d'exécution sur le thread de l'INTERFACE utilisateur, de sorte qu'il rend de lag.

Ma question est de savoir où est le meilleur endroit pour mettre de la Tâche.Exécuter.

Dois-je simplement (1) enveloppe extérieure appeler parce que c'est moins le filetage de travail .net ou devrais-je (2) enveloppe CPU seulement les méthodes liées à l'interne l'exécution de la Tâche.Exécuter ce qui la rend réutilisable pour d'autres lieux? Je ne suis pas sûr ici si le démarrage des travaux sur bg fils de profondeur dans la carotte est une bonne idée.

(1) le poing solution serait de le voir comme ça

   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 IO or CPU bound would now run in background

(2) la deuxième solution serait de le voir comme ça

   public async Task DoCpuBoundWorkAsync()
   {
        await Task.Run(() => {
          ...
          do lot of stuff here
          ...
          });
   }

   public async Task DoSomeOtherWorkAsync(
   {
        ... 
        not sure how to handle this methods - probably need to test one by one, if it is slowing down UI
        ...
   }

J'espère que cela a du sens. Merci pour vos avis à l'avance.

498voto

Stephen Cleary Points 91731

Remarque les lignes directrices pour effectuer un travail sur un thread d'INTERFACE utilisateur, recueillies sur mon blog:

  • Ne pas bloquer le thread d'INTERFACE utilisateur pour plus de 50ms à la fois.
  • Vous pouvez planifier ~100 continuations sur le thread d'INTERFACE utilisateur par seconde; 1000 c'est beaucoup trop.

Il existe deux techniques que vous devez utiliser:

1) Utiliser ConfigureAwait(false) quand vous le pouvez.

E. g., await MyAsync().ConfigureAwait(false); au lieu de await MyAsync();.

ConfigureAwait(false) raconte l' await que vous n'avez pas besoin de cv dans le contexte actuel (dans ce cas, "le contexte actuel" signifie "sur le thread d'INTERFACE utilisateur"). Cependant, pour le reste de l' async méthode (après l' ConfigureAwait), vous ne pouvez rien faire qui suppose que vous êtes dans le contexte actuel (par exemple, mise à jour des éléments de l'INTERFACE utilisateur).

Pour plus d'informations, voir mon article MSDN Meilleures Pratiques dans la Programmation Asynchrone.

2) Utiliser Task.Run d'appel CPU-bound méthodes.

Vous devez utiliser Task.Run, mais pas dans n'importe quel code vous voulez être réutilisable (c'est à dire, le code de bibliothèque). Si vous utilisez Task.Run à l' appel de la méthode, et non dans le cadre de la mise en œuvre de la méthode.

Donc, purement lié au PROCESSEUR de travail devrait ressembler à ceci:

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

Qui vous appel à l'aide d' Task.Run:

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

Les méthodes qui sont un mélange de CPU et I/O-lié doit avoir un Async signature de la documentation indiquant la ou les CPU de la nature:

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

Qui vous serait également appel à l'aide d' Task.Run (puisqu'il est en partie lié au PROCESSEUR):

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X