Je voudrais vous demander votre avis sur la bonne architecture à utiliser Task.Run
. J'expérimente une interface utilisateur laggy dans notre application WPF .NET 4.5 (avec le framework Caliburn Micro).
Fondamentalement, je fais (extraits de code très simplifiés):
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();
}
}
D'après les articles / vidéos que j'ai lus / vus, je sais que cela await
async
ne fonctionne pas nécessairement sur un fil d'arrière-plan et pour commencer à travailler en arrière-plan, vous devez l'envelopper avec attendre Task.Run(async () => ... )
. L'utilisation async
await
ne bloque pas l'interface utilisateur, mais elle fonctionne toujours sur le thread d'interface utilisateur, ce qui la rend retardée.
Où est le meilleur endroit pour mettre Task.Run?
Dois-je juste
Encapsulez l'appel externe car cela représente moins de travail de threading pour .NET
, ou dois-je envelopper uniquement les méthodes liées au processeur qui s'exécutent en interne,
Task.Run
car cela les rend réutilisables pour d'autres endroits? Je ne sais pas ici si commencer à travailler sur des threads d'arrière-plan profondément enfouis est une bonne idée.
Ad (1), la première solution serait la suivante:
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 deuxième solution serait la suivante:
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
}
la source
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
devrait simplement êtreawait Task.Run( () => this.contentLoader.LoadContentAsync() );
. AFAIK vous ne gagnez rien en ajoutant une secondeawait
et à l'async
intérieurTask.Run
. Et puisque vous ne passez pas de paramètres, cela simplifie un peu plusawait Task.Run( this.contentLoader.LoadContentAsync );
.Réponses:
Noter la directives pour effectuer un travail sur un thread d'interface utilisateur , collectées sur mon blog:
Vous devez utiliser deux techniques:
1) Utilisez
ConfigureAwait(false)
quand vous le pouvez.Par exemple,
await MyAsync().ConfigureAwait(false);
au lieu deawait MyAsync();
.ConfigureAwait(false)
indiqueawait
que vous n'avez pas besoin de reprendre sur le contexte actuel (dans ce cas, "sur le contexte actuel" signifie "sur le thread d'interface utilisateur"). Cependant, pour le reste de cetteasync
méthode (après leConfigureAwait
), vous ne pouvez rien faire qui suppose que vous êtes dans le contexte actuel (par exemple, mettre à jour les éléments de l'interface utilisateur).Pour plus d'informations, consultez mon article MSDN Best Practices in Asynchronous Programming .
2) Utilisez
Task.Run
pour appeler des méthodes liées au CPU.Vous devez utiliser
Task.Run
, mais pas dans le code que vous souhaitez être réutilisable (c'est-à-dire, le code de bibliothèque). Vous utilisez doncTask.Run
pour appeler la méthode, pas dans le cadre de la mise en œuvre de la méthode.Donc, le travail purement lié au CPU ressemblerait à ceci:
Que vous appelleriez en utilisant
Task.Run
:Les méthodes qui sont un mélange de lié au processeur et lié aux E / S doivent avoir une
Async
signature avec une documentation soulignant leur nature liée au processeur:Ce que vous appelleriez également en utilisant
Task.Run
(car il est partiellement lié au CPU):la source
ConfigureAwait(false)
. Si vous le faites en premier, vous constaterez peut-être que celaTask.Run
est complètement inutile. Si vous ne toujours besoinTask.Run
, alors il ne fait pas beaucoup de différence à l'exécution dans ce cas si vous appelez une ou plusieurs fois, alors faites tout ce qui est le plus naturel pour votre code.ConfigureAwait(false)
votre méthode liée au processeur, c'est toujours le thread d'interface utilisateur qui va faire la méthode liée au processeur, et seul tout ce qui suit peut être effectué sur un thread TP. Ou ai-je mal compris quelque chose?Task.Run
comprend les signatures asynchrones, donc il ne se terminera pas avant laDoWorkAsync
fin. Le supplémentasync
/await
n'est pas nécessaire. J'explique davantage le «pourquoi» dans ma série de blogs sur l'Task.Run
étiquette .TaskCompletionSource<T>
une de ses notations abrégées telles queFromAsync
. J'ai un article de blog qui explique plus en détail pourquoi les méthodes asynchrones ne nécessitent pas de threads .Un problème avec votre ContentLoader est qu'il fonctionne en interne de manière séquentielle. Un meilleur modèle consiste à paralléliser le travail, puis à se synchroniser à la fin, donc nous obtenons
De toute évidence, cela ne fonctionne pas si l'une des tâches nécessite des données d'autres tâches antérieures, mais devrait vous donner un meilleur débit global pour la plupart des scénarios.
la source