[framework 4] Les Tasks et le Thread UI
Je viens de passer quelques temps au TechDay’s et j’ai pu voir pas mal de session intéressante. Par contre une chose m’a un peu étonné lors de certaines de ces sessions qui abordaient les améliorations du framework .NET (donc le 4.5) : en gros, beaucoup de speaker expliquaient qu’avec l’API Task, il est très compliqué de revenir vers le thread UI sauf à passer par les méthodes old school (BeginInvoke,EndInvoke)…du coup, comme on se tape toute la tuyauterie, plus besoin de Task
.
Pour eux, la solution passe par l’utilisation de async/await ce qui va nous permettre de régler ce problème de manière élégante MAIS mon problème ce que pour moi, il existe des moyen technique assez simple (aussi simple que async/await) pour régler ce problème, en particulier avec PRISM.
Du coup, je me suis dit que ca valait peut être le coup de faire un petit post là dessus.
Je vais donc vous montrer ici comment je fais ca simplement avec la classe Eventaggregator (disponible avec PRISM)
Prenons l’exemple suivant:un bouton qui lorsque l’on click dessus renseigne le contenu d’une textBox avec un petit sleep pour simuler une activitée qui prend du temps:
1: private void button1_Click(object sender, RoutedEventArgs e)
2: {
3:
4: System.Threading.Thread.Sleep(5000);
5: textBox1.Text = "toto";
6:
7: }
si j’exécute mon appli, mon interface est gelée pendant 5 secondes…ce qui est inacceptable pour un utilisateur. Du coup, on souhaite faire ca en parallèle. Mon code devient donc:
1: private void button1_Click(object sender, RoutedEventArgs e)
2: {
3: var t = Task.Factory.StartNew(() =>
4: {
5: System.Threading.Thread.Sleep(5000);
6: textBox1.Text = "toto";
7: });
8:
9: }
…mais a l’exécution j’ai la fameuse erreur du Thread UI:

et pour régler ca, j’utilise l’EventAggregator:
1: public partial class MainWindow : Window
2: {
3: private IEventAggregator eventAggregator = new EventAggregator();
4: public MainWindow()
5: {
6: InitializeComponent();
7: eventAggregator.GetEvent<CompositePresentationEvent<string>>().
8: Subscribe(val => textBox1.Text = val
9: , ThreadOption.UIThread);
10: }
11:
12: private void button1_Click(object sender, RoutedEventArgs e)
13: {
14: var t = Task.Factory.StartNew<string>(() =>
15: {
16: System.Threading.Thread.Sleep(5000);
17: return "toto";
18: }).ContinueWith((task) =>
19: eventAggregator.GetEvent<CompositePresentationEvent<string>>()
20: .Publish(task.Result));
21: }
22:
23:
24: }
Il y a trois choses importantes dans ce code:
- On passe par un évènement pour synchroniser notre interface graphique avec notre tâche. Cet évènement est complètement géré par PRISM via la classe EventAggregator (qui est une pur merveille). Ici, j’ai utilisé l’objet de base (CompositePresentationEvent<string>) mais dans la pratique on va pouvoir avoir des objets plus complexes et mieux adaptés: si notre traitement renvoi un objet de type Client pour mettre à jours un ensemble de contrôles, on va plutôt utiliser la classe CompositePresentationEvent<Client>.
- L’utilisation de l’EventAggregator pour repasser dans le thread UI: la méthode Subscirbe qui permet de s’abonner à notre évènement à comme paramètre ThreadOption.UIThread, sans cette option, rien ne marche.
- A la fin de ma tâche, on lance une action qui à uniquement pour but de lever un évènement avec le résultat du traitement. Comme on est dans le ContinueWith, on est sûr que le traitement est fini et OK.
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :