Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

Multithreading in C# (Partie 3)

Par Riadh ADOUANI Publié le 12/10/2017 à 23:53:56 Noter cet article:
(0 votes)
En attente de relecture par le comité de lecture

Introduction

Comme vous l'avez vu dans les deux premiers chapitres, L’utilisation des tasks ajoute de performance pour vos applications. Mais lorsque vous travaillez sur des entrées / sorties (E / S), les choses vont un peu différemment.

Lorsque votre application exécute une opération d'E / S sur le thread d'application principal, Windows remarque que votre thread attend que l'opération d'E / S se termine. Peut-être que vous accédez à un fichier sur le disque ou sur le réseau, et cela pourrait prendre un certain temps.

Le concept de async et await

Pour cette raison, Windows interrompt votre thread afin qu'il n'utilise aucune ressource de processeur. Mais en faisant cela, il utilise toujours la mémoire, et le thread ne peut pas être utilisé pour servir d'autres requêtes, ce qui entraînera la création de nouveaux threads si des requêtes entrent en jeu.

Le code asynchrone résout ce problème. Au lieu de bloquer votre thread jusqu'à ce que l'opération d'E / S se termine, vous récupérez un objet Task qui représente le résultat de l'opération asynchrone. En définissant une opération de « Continuation » sur cette tâche, vous pouvez exécuter un autre traitement lorsque l'opération de l’E / S est terminée. En attendant, le pool des threads est disponible pour d'autres travaux. Lorsque l'opération d'E / S se termine, Windows notifie le runtime et la fonction « Continue » de la classe Tâche est planifiée sur le pool de threads.

Mais écrire du code asynchrone n'est pas aussi facile. Vous devez vous assurer que tous les cas des effets de bord sont traités et que rien ne peut aller mal. En raison de cette situation, C # 5 a ajouté deux nouveaux mots clés pour simplifier l'écriture du code asynchrone. Ces mots-clés sont async et await.

Vous utilisez le mot clé async pour marquer une méthode pour les opérations asynchrones. De cette façon, vous signalez au compilateur que quelque chose d'asynchrone va se produire. Le compilateur y répond en transformant votre code en machine d'état.

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Listings1
{
    class Program
    {
        private static string baseUrl = "https://jsonplaceholder.typicode.com";
        static void Main(string[] args)
        {
            var result = doSomeWork("/users")
                .ContinueWith(t=>
                {
                    Console.WriteLine(t.Result);
                });
            
            Console.ReadKey();
        }
        public static async Task<string> doSomeWork(string url)
        {
            
            string result = String.Empty;
            using (HttpClient client = new HttpClient())
            {
                result = await client.GetStringAsync(String.Concat(baseUrl, url));
            }
            return result;
        }
    }
}

GetStringAsync utilise son code asynchrone et renvoie une tâche <string> à l'appelant, entre temps le programme appelant peut exécuter autre traitements. Une fois la tâche est terminé le code la méthode ContinueWith est lancé.

La bonne chose à propos des qualificateurs async et await, c'est qu'ils ont instruit le compilateur à générer le bon code pour gérer le traitement asynchrone. Il est difficile d'écrire correctement le code asynchrone à la main, surtout quand vous voulez géré les exceptions. Faire cela correctement peut devenir difficile rapidement. L'ajout de tâches de continuation rompt également le flux logique du code. Votre code ne lit plus de haut en bas. Au lieu de cela, le flux de programme saute, et il est plus difficile à suivre lors du débogage de votre code. Le mot-clé await vous permet d'écrire du code qui semble synchro mais qui se comporte de manière asynchrone. Le débogueur Visual Studio est même assez intelligent pour vous aider à déboguer le code asynchrone comme s'il était synchrone.

Ainsi, une tâche liée au processeur est différente d'une tâche liée aux E / S. Les tâches liées au processeur utilisent toujours un thread pour exécuter leur travail. Une tâche liée aux E / S asynchrones n'utilise pas de thread tant que les E / S ne sont pas terminées.

Si vous construisez une application cliente qui doit rester réactif pendant l'exécution des opérations en arrière-plan, vous pouvez utiliser le mot clé await pour décharger une opération qui prend beaucoup de temps sur un autre thread. Bien que cela n'améliore pas les performances, il améliore la réactivité. Le mot clé await permet également de s'assurer que le reste de votre méthode s'exécute sur le thread d'interface utilisateur correct afin que vous puissiez mettre à jour l'interface utilisateur.

Faire une application évolutive qui utilise moins de threads est une autre histoire. Améliorer la scalabilité du code consiste à modifier l'implémentation réelle du code. Le code suivant montre un exemple :

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Listings2
{
    class Program
    {
        static void Main(string[] args)
        {
            goToSleepAsyncA(5000);
            goToSleepAsyncB(5000);
        }
        public static Task goToSleepAsyncA(int timeoutMS)
        {
            return Task.Run(() => Thread.Sleep(timeoutMS));
        }
        public static Task goToSleepAsyncB(int timeoutMS)
        {
            TaskCompletionSource<bool> tcpSource = null;
            var tm = new Timer((rt) => tcpSource.TrySetResult(true), null, -1, -1);
            tcpSource = new TaskCompletionSource<bool>(tm);
            tm.Change(timeoutMS, -1);
            return tcpSource.Task;
        }              
    }
}

        

La méthode goToSleepAsyncA utilise un thread du pool de threads quand il va passer à l’état inactif. La seconde méthode, cependant, qui a une implémentation complètement différente, n'occupe pas un thread en attendant que le temporisateur s'exécute. La seconde méthode vous donne une évolutivité par rapport à la première.

Juste vous devez garder cela à l'esprit, lorsque vous utilisez les mots clés async et wait, ne signifie pas que votre application est beaucoup plus performante. Cependant vous améliorez la réactivité de l’application et c’est un facteur très important dans l’expérience des utilisateurs.

Lorsqu'une exception se produit dans une méthode asynchrone, vous prévoyez normalement une exception Ag­gregateException. Cependant, le code généré vous permet de dissocier ces exceptions et de lancer la première de ses exceptions internes. Cela rend le code plus intuitif à utiliser et plus facile à déboguer

Une autre chose qui est importante lorsque vous travaillez avec le code asynchrone est le concept du contexte de synchronisation, qui relie son modèle d'application à son modèle de threading. Par exemple, une application WPF utilise un thread d'interface utilisateur unique et potentiellement plusieurs threads d'arrière-plan pour améliorer la réactivité et distribuer le travail sur plusieurs processeurs. Une application ASP.NET, cependant, utilise des threads du pool de threads qui sont initialisés avec les bonnes données, telles que l'utilisateur actuel, son CultureInfo pour répondre aux demandes entrantes.

Le SynchronizationContext résume le fonctionnement de ces différentes applications et garantit que vous vous retrouvez sur le bon thread lorsque vous devez mettre à jour quelque chose sur l'interface utilisateur ou traiter une demande Web.

Le mot clé await garantit que le SynchronizationContext actuel est sauvegardé et restauré à la fin de la tâche. Lorsque vous utilisez await dans une application WPF, cela signifie qu'après la fin de votre tâche, votre programme continue à s'exécuter sur le thread de l'interface utilisateur. Dans une application ASP.NET, le code restant est exécuté sur un thread contenant l'ensemble d'informations du client.

PLINQ

Le Language PLINQ est un langage de requête populaire du langage C #. Vous pouvez l'utiliser pour effectuer des requêtes sur toutes sortes de données.

La requête PLINQ (Parallel Language-Integrated Query) peut être utilisée sur des objets pour transformer potentiellement une requête séquentielle en une requête parallèle.

Les méthodes d'extension pour utiliser PLINQ sont définies dans la classe System.Linq.ParallelEnumerable. Vous pouvez utiliser les versions des opérateurs parallèles LINQ, telles que Where, Select, SelectMany, GroupBy, Join, OrderBy, Skip et Take.

La code suivant montre comment convertir une requête en une requête parallèle.

using System;
using System.Linq;

namespace Listings4
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(1, 100);
            var results = values.AsParallel()
                .Where(n => n%3 ==0).ToArray();
            Console.WriteLine("Multiple of 3 from 1 to 100");
            foreach (var value in results)
            {
                Console.WriteLine(value);
            }
            Console.ReadKey();
        }
      
    }
}

Output
Multiple of 3 from 1 to 100
3
6
9
12
15
18
21
24
27
30
33
36
39
42
45
48
51
54
57
60
63
66
69

using System;
using System.Linq;

namespace Listings5
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(1, 100);
            var results = values.AsParallel()
                .Where(n => n>1 ? Enumerable.Range(1, n).Where(x => n % x == 0)
                             .SequenceEqual(new[] { 1, n }) : false)
                             .ToArray();
            Console.WriteLine("Prime number from 1 to 100");
            foreach (var value in results)
            {
                Console.WriteLine(value);
            }
            Console.ReadKey();
        }
      
    }
}

       

Si vous avez une requête complexe qui peut bénéficier d'un traitement parallèle mais qui comporte également des parties séquentielles, vous pouvez utiliser AsSequential pour empêcher que votre requête soit traitée en parallèle. Un scénario où vous voulez préservez l’ordre de votre requête est listé dans cet exemple. Il montre comment vous pouvez utiliser l'opérateur AsSequential pour vous assurer que la méthode Take ne gâche pas votre commande

using System;
using System.Linq;

namespace Listings6
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(1, 100);
            var results = values.AsParallel()
                .AsOrdered()    
                .Where(n => n % 3 == 0)
                .AsSequential();               

           
            Console.WriteLine("The 10 multiples of 3 ");
            foreach (var value in results.Take(10))
            {
                Console.WriteLine(value);
            }
            Console.ReadKey();
        }
      
    }
}
The 10 multiples of 3
3
6
9
12
15
18
21
24
27
30

       

Lors de l'utilisation de PLINQ, vous pouvez utiliser l'opérateur ForAll pour parcourir une collection d’une manière Parallèle. Le code suivant montre comment faire cela.

using System;
using System.Linq;

namespace Listings7
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(1, 100);
            var results = values.AsParallel()
                .Where(n => n % 3 == 0);           
            Console.WriteLine("Multiple of 3 from 1 to 1000 ");
            results.ForAll(value => Console.WriteLine(value));
            Console.ReadKey();
        }
      
    }
}

Multiple of 3 from 1 to 100
003
015
018
021
024
027
030
033
036
039
042
045
048
051
054
057
060
063
066
069

       

Contrairement à foreach, ForAll n'a pas besoin de tous les résultats avant de commencer à s'exécuter. Dans cet exemple, ForAll, cependant, supprime tout tri utilisé.

Bien sûr, il peut arriver que certaines des opérations de votre requête parallèle lèvent une exception. Le .NET Framework gère cela en agrégeant toutes les exceptions en une exception Aggrega­teException. Cette exception expose une liste de toutes les exceptions qui se sont produites pendant l'exécution parallèle. La code suivant montre comment vous pouvez gérer cela :

using System;
using System.Linq;

namespace Listings8
{
    class Program
    {
        static void Main(string[] args)
        {
            var numbers = Enumerable.Range(1, 1000);
            try
            {
                var parallelResult = numbers.AsParallel().Where(i => isPrime(i));
                parallelResult.ForAll(e => Console.WriteLine(e));               
            }
            catch (AggregateException e)
            {
                Console.WriteLine($"There where Exception {e.Message}");
            }
            finally
            {
                Console.ReadKey();
            }
        }
        public static bool isPrime(int n)
        {
            if (n % 500 == 0)
                throw new InvalidOperationException();
            return n > 1 ? Enumerable.Range(1, n).Where(x => n % x == 0)
                  .SequenceEqual(new[] { 1, n }) : false;
        }
    }

}


       
A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels