{"id":493,"date":"2012-08-01T16:19:19","date_gmt":"2012-08-01T14:19:19","guid":{"rendered":"http:\/\/blog.khaledtannir.net\/?p=493"},"modified":"2012-08-01T16:19:19","modified_gmt":"2012-08-01T14:19:19","slug":"implementer-mapreduce-avec-tpl-task-parallel-library","status":"publish","type":"post","link":"https:\/\/khaledtannir.net\/en\/2012\/08\/01\/implementer-mapreduce-avec-tpl-task-parallel-library\/","title":{"rendered":"Impl\u00e9menter MapReduce avec TPL (Task Parallel Library)"},"content":{"rendered":"<p>Dans cet article je vous propose une impl\u00e9mentation de MapReduce avec TPL (<em>Task Parallel Library<\/em>) du framework .NET 4.0. Si vous ne connaissez pas MapReduce, vous avez toujours la possibilit\u00e9 de consulter mon article (<a title=\"MapReduce \u2013 1\u00e8re Partie\" href=\"https:\/\/khaledtannir.net\/blog\/2011\/05\/08\/mapreduce\/\">partie 1<\/a> et <a title=\"MapReduce \u2013 2\u00e8me Partie\" href=\"https:\/\/khaledtannir.net\/blog\/2011\/05\/14\/mapreduce-2eme-partie\/\">partie 2<\/a>) d\u00e9di\u00e9 \u00e0 sa pr\u00e9sentation et \u00e0 son mode op\u00e9ratoire.<br \/>\n<!--more--><\/p>\n<h2><span style=\"color: #008000\">Introduction :<\/span><\/h2>\n<p>J&#8217;ai voulu dans cet article proposer une impl\u00e9mentation possible du mod\u00e8le de traitement parall\u00e8le MapReduce. Bien s\u00fbr il ne s&#8217;agit pas d&#8217;impl\u00e9menter toutes les fonctionnalit\u00e9s du mod\u00e8le cr\u00e9\u00e9 par Google mais plut\u00f4t la fonction Map qui effectue des traitements en parall\u00e8le et la fonction Reduce qui, quant \u00e0 elle, agr\u00e8ge les r\u00e9sultats avant de nous les retourner.<br \/>\nConcevoir une application qui g\u00e8re et synchronise plusieurs dizaines de threads reste une t\u00e2che difficile. Faire de la programmation parall\u00e8le est aussi une t\u00e2che difficile et les API de d\u00e9veloppement sont g\u00e9n\u00e9ralement complexes et demandent des efforts d\u2019architecture\/programmation\/d\u00e9bug afin de rendre le tout utilisable.<br \/>\nPartant de ce principe j&#8217;ai fait le choix d&#8217;utiliser\u00a0TPL (<em>Task Parallel Library<\/em>) du framework .NET 4.0. Cette librairie regroupe des classes permettant\u00a0de parall\u00e9liser des traitements.\u00a0Ces classes permettant une ex\u00e9cution des traitements en parall\u00e8le,\u00a0g\u00e8rent \u00e9galement la pr\u00e9sence de coeurs multiples de la machine ex\u00e9cutant le code\u00a0de l&#8217;application.<br \/>\n<span style=\"color: #008000\">TPL\u00a0est compos\u00e9 de 8 classes et de 3 namespaces principaux (.NET 4) :<\/span><\/p>\n<ol>\n<li><span style=\"color: #0000ff\">System.Threading.Tasks<\/span> : Ce namespace contient la base de Task Parallel Library (TPL), il contient la classe principale de la technologie : la classe Tasks (une sorte de \u201csuper Thread\u201d)<\/li>\n<li><span style=\"color: #0000ff\">System.Collections.Concurrent<\/span> : Ce namespace contient les collections pouvant \u00eatre utilis\u00e9 dans des contextes multithreads (thread safe).<\/li>\n<li><span style=\"color: #0000ff\">System.Linq<\/span> : LINQ a \u00e9t\u00e9 am\u00e9lior\u00e9 pour int\u00e9grer des aides au multithreading. Connu sous le nom de PLINQ (Parallel LINQ), il permet de parall\u00e9liser certaines \u00e9tapes des requ\u00eates LINQ afin de les rendre plus rapide.<\/li>\n<\/ol>\n<p>Le namespace <span style=\"color: #0000ff\">System.Threading.Task<\/span>\u00a0contient une classe <em><span style=\"color: #0000ff\">Task<\/span><\/em>. Cette classe repr\u00e9sente une op\u00e9ration asynchrone. Ainsi on d\u00e9fini notre op\u00e9ration (t\u00e2che) et, lorsqu\u2019on la d\u00e9marre, celle-ci est assign\u00e9 par le <em><span style=\"color: #0000ff\">TaskScheduler<\/span> <\/em>\u00e0 un des coeurs disponibles de la machine. Le fonctionnement de ce syst\u00e8me est bas\u00e9 sur\u00a0une\u00a0 file de type FIFO (first in \/ first out) prenant en compte un niveau de\u00a0priorit\u00e9. Le <em>Taskscheduler<\/em> int\u00e8gre un m\u00e9canisme de r\u00e9partition de charge et, est capable de distribuer un grand nombre de t\u00e2ches aux diff\u00e9rentes c\u0153urs et de &#8220;balancer&#8221; la charge si un c\u0153ur est plus occup\u00e9 qu\u2019un autre. L&#8217;utilisation de TPL reste simple,\u00a0 la cr\u00e9ation et l\u2019utilisation de la classe <em><span style=\"color: #0000ff\">Task<\/span> <\/em>est \u00e9galement simple.<br \/>\nL&#8217;impl\u00e9mentation que je propose se base sur la classe <em><span style=\"color: #0000ff\">Task<\/span> <\/em>et utilise ses m\u00e9canismes de synchronisations pour\u00a0synchroniser les diff\u00e9rentes op\u00e9rations. Pour sa mise en oeuvre l&#8217;application, tr\u00e8s simplissime, <em>TPLMapReduce\u00a0\u00a0<\/em>impl\u00e9mente une classe qui contient les m\u00e9thodes n\u00e9cessaires \u00e0 l\u2019ex\u00e9cution du code.<\/p>\n<h2><span style=\"color: #008000\">L&#8217;application TPLMapReduce :<\/span><\/h2>\n<p>Pour illustrer le fonctionnement de MapReduce, l&#8217;application TPLMapReduce aura comme objectif de compter les mots trouv\u00e9s dans 4 documents de type texte. Deux impl\u00e9mentations sont propos\u00e9es pour effectuer cette t\u00e2che ; la premi\u00e8re impl\u00e9mentation consiste \u00e0 cr\u00e9er plusieurs op\u00e9rations de type Map puis agr\u00e9ger le tous avec une seule op\u00e9ration de type Reduce. La deuxi\u00e8me impl\u00e9mentation consiste \u00e0 attribuer \u00e0 chaque docuement un ensemble de t\u00e2ches Map puis une fonction Reduce d\u00e9di\u00e9e afin d\u2019agr\u00e9ger les donn\u00e9es de celui-ci. Au final le r\u00e9sultat global est le m\u00eame sauf que je voulais garder les portes de l&#8217;imagination et de la cr\u00e9ativit\u00e9 ouvertes.<br \/>\nPour commencer, la classe MapReduce est une classe absrtaite et ne peut pas \u00eatre instanci\u00e9e directement, ceci dans le but de permettre son extension ind\u00e9pendamment des classes qui pourront l&#8217;utiliser. Elle impl\u00e9mente deux propri\u00e9t\u00e9s Map et Reduce. Ces propri\u00e9t\u00e9s d\u00e9finissent les fonctions Map et Reduce respectivement. Ces fonctions seront d\u00e9finies dans les classes filles de la classe MapReduce.<br \/>\nLa fonction Map acc\u00e8pte un type de donn\u00e9es en entr\u00e9e (par exemple string) et retourne un type de donn\u00e9es interm\u00e9diaire qui peut \u00eatre du m\u00eame type que les donn\u00e9es en entr\u00e9es ou un type diff\u00e9rent. Ceci est la signature de notre fonction Map.<\/p>\n<pre class=\"brush:csharp\"><code>\/\/\/ &lt;summary&gt;\n\/\/\/ Obtient ou d\u00e9finit la fonction Map\n\/\/\/ &lt;\/summary&gt;\npublic virtual Func&lt;TInput, TPartialResult&gt; Map { get; protected set; }<\/code><\/pre>\n<p class=\"brush:csharp\">La fonction Reduce quant \u00e0 elle doit prendre en entr\u00e9e l&#8217;ensemble des r\u00e9sultats interm\u00e9diaires des t\u00e2ches Map et nous retourner\u00a0 un r\u00e9sultat. Pour cela, sa signature est la suivante :<\/p>\n<pre class=\"brush:csharp\"><code>\/\/\/ &lt;summary&gt;\n\/\/\/ Obtient ou d\u00e9finit la fonction Reduce\n\/\/\/ &lt;\/summary&gt;\npublic virtual Func&lt;TPartialResult[], TOutput&gt; Reduce { get; protected set; }<\/code><\/pre>\n<p class=\"brush:csharp\">Remarquez que la modification des deux\u00a0propri\u00e9t\u00e9s Map et Reduce ont \u00e9t\u00e9 restrintes\u00a0aux classes filles uniquement.<\/p>\n<h3 class=\"brush:csharp\"><span style=\"color: #008000\">Les op\u00e9rations Map :<\/span><\/h3>\n<p class=\"brush:csharp\">Les cr\u00e9ation des t\u00e2ches Map est de la responsabilit\u00e9 de la m\u00e9thode <em>CreateMapTasks<\/em>. Cette m\u00e9thode accepte un tableau contenant les donn\u00e9s en entr\u00e9es auxquelles le traitement doit \u00eatre appliqu\u00e9.<\/p>\n<pre class=\"brush:csharp\"><code>\/\/\/ &lt;summary&gt;\n\/\/\/ Cr\u00e9\u00e9 les diff\u00e9rentes taches Map\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;param name=\"map\"&gt;La fonction Map&lt;\/param&gt;\n\/\/\/ &lt;param name=\"inputs\"&gt;Tableau des donn\u00e9es en entr\u00e9e du type TInput&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;Retourne un tableau contenant le r\u00e9sultats interm\u00e9diaires du traitement&lt;\/returns&gt;\nprotected virtual Task&lt;TPartialResult&gt;[] CreateMapTasks(TInput[] inputs)\n{\n   var tasks = new Task&lt;TPartialResult&gt;[inputs.Length];\n       for (int i = 0; i &lt; inputs.Length; ++i)\n       {\n           tasks[i] = Task.Factory.StartNew(() =&gt; Map(inputs[i]));\n       }\n   return tasks;\n}<\/code><\/pre>\n<p>Le code de cette m\u00e9thode est relativement simple. Ici je parcours le tableau des donn\u00e9es en entr\u00e9es et pour chacune des\u00a0lignes du\u00a0tableau je cr\u00e9\u00e9 une nouvelle instance de la classe <em><span style=\"color: #0000ff\">Task\u00a0<\/span><\/em>en appelant la m\u00e9thode <em><span style=\"color: #0000ff\">Factory<\/span> <\/em>de cette classe.\u00a0 Une op\u00e9ration de type <em><span style=\"color: #0000ff\">Task<\/span> <\/em>est un\u00a0conteneur pour une op\u00e9ration qui sera ex\u00e9cut\u00e9e de fa\u00e7on asynchrone. Il suffit donc de cr\u00e9er une instance de la classe <em><span style=\"color: #0000ff\">Task<\/span><\/em>, de lui assigner un traitement \u00e0 r\u00e9aliser et de l&#8217;ex\u00e9cuter. Le runtime .Net d\u00e9termine automatiquement combien d&#8217;instances <em><span style=\"color: #0000ff\">Task<\/span> <\/em>(ou t\u00e2ches) il est possible d&#8217;ex\u00e9cuter simultan\u00e9ment et ceci en fonction du nombre de coeurs disponibles, puis\u00a0les g\u00e8re au travers du <em><span style=\"color: #0000ff\">ThreadPool<\/span> <\/em>qui est le \u00ab scheduler \u00bb par d\u00e9faut de le la TPL. Il est possible d&#8217;ex\u00e9cuter les Tasks dans un ordre donn\u00e9, d&#8217;attendre la fin d&#8217;une Task pour en ex\u00e9cuter une autre, une Task peut retourner ou non un r\u00e9sultat, etc.<br \/>\nDans l&#8217;extrait de\u00a0code ci-dessus, j&#8217;ai fait appel \u00e0 la m\u00e9thode <em><span style=\"color: #0000ff\">StartNew()<\/span> <\/em>afin de d\u00e9marrer imm\u00e9diatement la t\u00e2che sans \u00eatre oblig\u00e9 par la suite de boucler sur l&#8217;ensemble des t\u00e2ches et d\u2019appeler la m\u00e9thode <em><span style=\"color: #0000ff\">Start()<\/span> <\/em>sur chacune des t\u00e2ches.<br \/>\nA la fin du traitement de chacune des t\u00e2ches, la propri\u00e9t\u00e9 <em><span style=\"color: #0000ff\">Result<\/span> <\/em>contiendra le r\u00e9sultat du traitement appliqu\u00e9 et son\u00a0type sera celui\u00a0d\u00e9finit par la fonction Map.<\/p>\n<h3><span style=\"color: #008000\">Les op\u00e9rations Reduce :<\/span><\/h3>\n<p>Les op\u00e9rations Reduce quant \u00e0 elles doivent \u00eatre ex\u00e9cut\u00e9es une fois toutes les op\u00e9rations Map ont termin\u00e9 leur travail. Pour assurer cette synchronisation, j&#8217;ai utilis\u00e9 la m\u00e9thode ContinueWhenAll() de la Classe Factory imbriqu\u00e9e \u00e0 la classe Task. En effet cette m\u00e9thode va attendre que la totalit\u00e9 des taches du tableau mapTasks en entr\u00e9e\u00a0aient termin\u00e9 leurs traitements avant d&#8217;appeler l&#8217;op\u00e9ration Reduce.<\/p>\n<pre class=\"brush:csharp\"><code>\/\/\/ &lt;summary&gt;\n\/\/\/ Cr\u00e9\u00e9 les diff\u00e9rentes taches Reduce\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;param name=\"mapTasks\"&gt;Tableau des taches Map ayant \u00e9t\u00e9 ex\u00e9cut\u00e9es&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;Retourne le r\u00e9sultat de l'application de la fonction Reduce typ\u00e9 selon TOutput&lt;\/returns&gt;\nprotected virtual Task&lt;TOutput&gt; CreateReduceTask(Task&lt;TPartialResult&gt;[] mapTasks)\n{\n    return Task.Factory.ContinueWhenAll(mapTasks, tasks =&gt; PerformReduce(tasks));\n}<\/code><\/pre>\n<h3><\/h3>\n<h3><span style=\"color: #008000\">Exemple d&#8217;utilisation :<\/span><\/h3>\n<p>Les fichiers textes (format UTF-8) utilis\u00e9s dans cet exemple sont disponibles sur le site du <a title=\"projet Gutenberg\" href=\"http:\/\/www.gutenberg.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">Projet Gutenberg<\/a>.<br \/>\nAfin de compter tous les mots qui apparaissent dans ces documents, ceux-ci n\u00e9cessitent une petite phase de pr\u00e9traitement en vue de leur pr\u00e9paration \u00e0 cette t\u00e2che. Pour cela, j&#8217;ai choisi de les lire en parall\u00e8le puis de les d\u00e9couper en lignes. A la fin de ce pr\u00e9traitement, chaque ligne du tableau contiendra une ligne extraite du fichier texte. Bien s\u00fbr qu&#8217;il s&#8217;agit d&#8217;un traitement sommaire pour les besoins de l&#8217;illustration et non d&#8217;un traitement en profondeur.<\/p>\n<pre class=\"brush:csharp\"><code>\/\/ D\u00e9finition des fichiers dont les mots devront \u00eatre compt\u00e9s\n            string[] files = {\n                                \"pg1041.txt\",\n                                \"pg1105.txt\",\n                                \"pg1745.txt\",\n                                \"pg25979.txt\"\n                             };\n            char[] delimiters = { ' ', ',', ';', '.' };\n            string[][] inputs = new string[files.Length][];\n            \/\/ Lecture en parall\u00e8le des fichiers, chaque fichiers est d\u00e9coup\u00e9 en un tableau de lignes\n            for (int i = 0; i &lt; files.Length; i++)\n            {\n                inputs[i] = File.ReadLines(files[i]).AsParallel().SelectMany(line =&gt; line.Split(delimiters)).ToArray();\n            }<\/code><\/pre>\n<p class=\"brush:csharp\">La classe <span style=\"color: #0000ff\">Exemple 1<\/span> h\u00e9rite de la classe MapReduce et permet de la concr\u00e9tiser avec la d\u00e9finition des deux fonctions Map et Reduce. L&#8217;impl\u00e9mentation de la classe MapReduce implique la d\u00e9finition de trois types g\u00e9n\u00e9riques :<\/p>\n<ul>\n<li>\n<div class=\"brush:csharp\">Type en entr\u00e9e, string dans notre cas.<\/div>\n<\/li>\n<li>\n<div class=\"brush:csharp\">Type Interm\u00e9diaire, un tableau de Cl\u00e9,Valeur dans notre cas.<\/div>\n<\/li>\n<li>\n<div class=\"brush:csharp\">Type de retour, un tableau de Cl\u00e9,Valeur dans notre cas.<\/div>\n<\/li>\n<\/ul>\n<pre class=\"brush:csharp\">\u00a0\u00a0\u00a0 <code>\/\/\/ &lt;summary&gt;\n    \/\/\/ Exemple d'utilisation de l'impl\u00e9mentation PLinq MapReduce\n    \/\/\/ &lt;\/summary&gt;\n    class Exemple1 : MapReduce&lt;string, KeyValuePair&lt;string, int&gt;[], KeyValuePair&lt;string, int&gt;[]&gt;\n    {\n        public Exemple1()\n        {\n            \/\/M\u00e9thode Map : On d\u00e9coupe la chaine en entr\u00e9e en mot entier et on g\u00e9n\u00e8re une paire Cl\u00e9,Valeur (valeur = 1)\n            \/\/ pour chaque mot trouv\u00e9\n            base.Map = (s =&gt; s.Split(\" \".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)\n            .SelectMany(g =&gt; new[] { new KeyValuePair&lt;string, int&gt;(g, 1) })\n            .ToArray());\n            \/\/M\u00e9thode Reduce : On regrouppe l'ensemble des Cl\u00e9s g\u00e9n\u00e9r\u00e9es par la m\u00e9thode Map et on effectue la somme\n            \/\/(ou le count car la valeur est \u00e9gale \u00e0 1). Le tout est stock\u00e9 dans un nouvel ensemble de Cl\u00e9s,Valeurs\n            base.Reduce = (kv =&gt; kv.SelectMany(k =&gt; k)\n            .GroupBy(g =&gt; g.Key)\n            .SelectMany(g =&gt; new[] { new KeyValuePair&lt;string, int&gt;(g.Key, g.Count()) })\n            .ToArray());\n        }\n    }<\/code><\/pre>\n<p class=\"brush:csharp\">La fonction Map d\u00e9coupe chaque ligne de texte en entr\u00e9e en plusieurs mots (deux mots sont s\u00e9par\u00e9s g\u00e9n\u00e9ralement par un espace), puis pour chaque mot trouv\u00e9 cr\u00e9\u00e9 une instance de la classe KeyValuePair en indiquant comme cl\u00e9 le mot trouv\u00e9 et 1 comme valeur, le tout est retourn\u00e9 \u00e0 l&#8217;appelant sous forme d&#8217;un tableau.<\/p>\n<p class=\"brush:csharp\">La fonction Reduce &#8220;aplatit&#8221; le tableau en entr\u00e9e contenant toutes les paires Cl\u00e9,Valeur en le transformant en un seul tableau \u00e0 deux dimensions puis effectue une op\u00e9ration de regroupement. A nouveau une nouvelle paire de Cl\u00e9,Valeur est cr\u00e9\u00e9e afin de stocker chaque nouveau r\u00e9sultat.<\/p>\n<p class=\"brush:csharp\">Il ne reste plus qu&#8217;\u00e0 instancier la classe <strong><span style=\"color: #0000ff\">Exemple 1<\/span> <\/strong>et d\u2019ex\u00e9cuter sa m\u00e9thode <strong>Run()<\/strong> pour r\u00e9cup\u00e9rer le r\u00e9sultat.<\/p>\n<pre class=\"brush:csharp\"><code>\/\/ Ex\u00e9cution de l'exemple 1\nExemple1 ex = new Exemple1();\nvar results = ex.Run(inputs);<\/code><\/pre>\n<p class=\"brush:csharp\">La classe <span style=\"color: #0000ff\">Exemple 2<\/span> illustre la deuxi\u00e8me proposition d&#8217;impl\u00e9mentation en red\u00e9finissant la m\u00e9thode <em><span style=\"color: #0000ff\">Run()<\/span> <\/em>de la classe MapReduce.<\/p>\n<pre class=\"brush:csharp\"><code> \/\/\/ &lt;summary&gt;\n        \/\/\/ M\u00e9thode Run de la classe de base red\u00e9finie.\n        \/\/\/ Pour chaque sous-tableau, on cr\u00e9e un ensemble de t\u00e2ches Map et Une t\u00e2che Reduce\n        \/\/\/ &lt;\/summary&gt;\n        \/\/\/ &lt;param name=\"inputs\"&gt;Tableau contenant les diff\u00e9rents tableaux des donn\u00e9es en entr\u00e9e ayant le type TInput&lt;\/param&gt;\n        \/\/\/ &lt;returns&gt;Retourne le r\u00e9sultats du type TOutput du traitement&lt;\/returns&gt;\n        public override Task&lt;KeyValuePair&lt;string, int&gt;[]&gt; Run(string[][] inputs)\n        {\n            \/\/Cr\u00e9ation du tableau qui contiendra les resultats des diff\u00e9rents traitements\n            var tasks = new Task&lt;KeyValuePair&lt;string, int&gt;[]&gt;[inputs.Length];\n            for (int i = 0; i &lt; inputs.Length; i++)\n            {\n                tasks[i] = Run(inputs[i]);\n            }\n           return CreateReduceTask(tasks);\n        <\/code>}<\/pre>\n<div id=\"attachment_535\" style=\"width: 278px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/khaledtannir.net\/en\/wp-content\/uploads\/sites\/2\/2020\/05\/TPLMapReduce.jpg\"><img aria-describedby=\"caption-attachment-535\" loading=\"lazy\" class=\"wp-image-535\" title=\"Console TPLMapReduce \" src=\"http:\/\/blog.khaledtannir.net\/wp-content\/uploads\/2012\/08\/TPLMapReduce-150x150.jpg\" alt=\"Console TPLMapReduce \" width=\"268\" height=\"268\" \/><\/a><p id=\"caption-attachment-535\" class=\"wp-caption-text\">Console TPLMapReduce<\/p><\/div>\n<h2 class=\"brush:csharp\"><span style=\"color: #008000\">Conclusion :<\/span><\/h2>\n<p class=\"brush:csharp\">Dans cet article j&#8217;ai propos\u00e9 une impl\u00e9mentation simplissime de MapReduce avec TPL (<em>Task Parallel Library<\/em>) b\u00e9n\u00e9ficiant ainsi de toutes ses fonctionnalit\u00e9s de cr\u00e9ation, de gestion et de synchronisation de threads. Nous avons vu comment il est \u00e9tait de mettre en oeuvre cette libraire fournie par le framework .NET 4.0.<\/p>\n<p class=\"brush:csharp\">Dans cet article\u00a0il ne s&#8217;agit que d&#8217;une introduction \u00e0 cette impl\u00e9mentation et ne pr\u00e9tend pas \u00eatre exhaustif. Cependant plusieurs am\u00e9liorations peuvent \u00eatre apport\u00e9es en vue de son am\u00e9lioration.<\/p>\n<h3 class=\"brush:csharp\"><\/h3>\n<h3 class=\"brush:csharp\"><\/h3>\n<h3 class=\"brush:csharp\"><span style=\"color: #008000\">Download :<\/span><\/h3>\n<p class=\"brush:csharp\"><a href=\"https:\/\/khaledtannir.net\/en\/wp-content\/uploads\/sites\/2\/2020\/05\/TPLMapReduce.zip\">TPLMapReduce<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dans cet article je vous propose une impl\u00e9mentation de MapReduce avec TPL (Task Parallel Library) du framework .NET 4.0. Si vous ne connaissez pas MapReduce, vous avez toujours la possibilit\u00e9 de consulter mon article (partie 1 et partie 2) d\u00e9di\u00e9 \u00e0 sa pr\u00e9sentation et \u00e0 son mode op\u00e9ratoire.<\/p>\n","protected":false},"author":2,"featured_media":3508,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[54,55,61],"tags":[65,80],"_links":{"self":[{"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/posts\/493"}],"collection":[{"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/comments?post=493"}],"version-history":[{"count":0,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/posts\/493\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/media\/3508"}],"wp:attachment":[{"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/media?parent=493"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/categories?post=493"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/khaledtannir.net\/en\/wp-json\/wp\/v2\/tags?post=493"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}