Billet sur l'extension parallèle à .NET PFX et C++/CLI

Eric Vernié vient de nous préparer un billet sur PFX, il m'honore en me réservant la primeur de celui-ci :-) Bonne lecture !

Vous avez sans doute entendu parler de cette extension nommée PFX qui simplifie le développement parallèle pour les développeurs .NET.

Que vous pouvez télécharger ici :

Je n’y reviendrais pas en détail dans ce billet.

Par contre, j’ai vu de ci de la quelques développeurs, qui essayaient de traduire du C# vers du C++/CLI, et bien sur cela ne compilait pas en C++/CLI.

Ceci pour plusieurs raisons.

Tout d’abord C# permet ce qu’on appel les délégués anonymes.

Exemple :

Parallel.For(0, MAX, delegate (int i)

{

vecteurs[i] = "Carré : " + (i * i).ToString() + " ThreadID :" + Thread.CurrentThread.ManagedThreadId.ToString();

});

Or cette notation n’existe pas en C++/CLI, par contre les délégués existent. Il suffit donc de décomposer le code de la manière suivante :

Par exemple :

System::Action<int>^ action=gcnew System::Action<int>(&MonAction);

Parallel::For(0,MAX,action);

static void MonAction(int i)

{

_vecteurs[i] = "Carré : " + (i * i).ToString() + " ThreadID :" + Thread::CurrentThread->ManagedThreadId.ToString();

}

Note : Parallel::For() est une méthode statique de la bibliothèque PFX qui permet d’éclater une boucle sur le nombre de processeurs disponibles. Pour ceux qui ont l’habitude de ‘OpenMP’ c’est le même principe.

Dans le même ordre d’idée cette nouvelle bibliothèque utilise la syntaxe Parallel ::ForEach() ;

Calcul ^calcul2=gcnew Calcul(vecteurs);

System::Action<long>^ action2=gcnew System::Action<long>(calcul2,&Calcul::SommeSafe );

Parallel::ForEach(vecteurs,action2);

Note : vecteurs étant un Vector de type STL/CLR (nouveau avec VS 2008) cliext::vector<long>^ _vecteurs;

Il est possible également très simplement d’exécuter plusieurs travaux en parallèle à l’aide de la méthode statique DO de la class Parallel.

static void DumpException(Exception^ ex)

{

Console::WriteLine (ex->InnerException->Message);

}

ref class Message

{

private: String^ _message;

       public :

Message(String^ message)

{

       _message=message;

}

void AfficherMessage()

{

Console::WriteLine("Thread ID :" + Thread::CurrentThread->ManagedThreadId.ToString());

Console::WriteLine (_message);

System::Threading::Thread::Sleep (1000);

//Les erreurs sont encapsulées dans une erreur de niveau supèrieure AggregateException

throw gcnew ArithmeticException("Erreur ThreadID" + Thread::CurrentThread->ManagedThreadId.ToString());

        }

       };

static void TestDO()

       {

Message m1("Bonjour");

Message m2("et bienvenue");

Message m3("au");

Message m4("Techdays 2008");

System::Action^ action1=gcnew System::Action(%m1,&Message::AfficherMessage);

             System::Action^ action2=gcnew System::Action(%m2,&Message::AfficherMessage);

             System::Action^ action3=gcnew System::Action(%m3,&Message::AfficherMessage);

             System::Action^ action4=gcnew System::Action(%m4,&Message::AfficherMessage);

        try

            {

             Parallel::Do(action1,action2,action3,action4);

                             

            }

    catch (AggregateException^ agrEx)

            {

 //Convertir une collection .NET en vecteur STL/CLR;

 cliext::vector<Exception^> ^vecteursException= gcnew cliext::vector<Exception^>(agrEx->InnerExceptions);

 cliext::vector<Exception^>::iterator begin=vecteursException->begin() ;

 cliext::vector<Exception^>::iterator end=vecteursException->end() ;

 System::Action<Exception^> ^dumpException=gcnew System::Action<Exception^>(&DumpException);

 cliext::for_each (begin,end,dumpException);

            }

}

A noter que toutes les erreurs sont encapsulées dans une erreur de plus haut niveau AggregateException et qui ne sera levée que lorsque toutes les méthodes seront terminées et que pour parcourir la liste des erreurs, j’utilise cliext ::for_each() de la STL/CLR (nouveau en VS 2008)

La bibliothèque d’extension parallèle, permet également de travailler plus finement avec des objets de plus bas niveau comme Task et Future.

Par exemple, si nous souhaitons exécuter sur deux processeurs, deux méthodes qui font un calcul, puis faire la somme des résultats obtenus, nous devons mettre en place un mécanisme compliqué de synchronisation et d’attente.

Avec C# et la class Future c’est assez simple :

Future<int> z=Future.Create (()=>Carre(42));

Future<int> w = Future.Create(() => Carre(42));

Console.WriteLine("La somme des carrés {0}", w.Value + z.Value);

En C# ici, ont utilise une notation qui simplifie l’utilisation des délégués anonymes les Lambda expressions.

Les lambdas expression n’existant pas en C++/CLI nous devons utiliser encore une fois les délègués de la manière suivante :

ref struct CalculCarre

       {

private :

       int _a;

public :

CalculCarre(int a)

{

       _a=a;

}

int Carre()

{

       return _a*_a;

}

};

static void CalculFuture()

{

CalculCarre c(42);

System::Func<int> ^ func=gcnew System::Func<int>(%c,&CalculCarre::Carre);

Future<int>^ z=Future::Create (func);

Future<int>^ w=Future::Create(func);

Console::WriteLine("La somme des carrés {0}", z->Value + w->Value );

}

La classe Future est élégante, car c’est la propriété Value, qui met en place le mécanisme d’attente. En d’autre terme l’addition de z+w se fera lorsque l’éxécution deux méthodes sera achevée.

 

Merci Eric

A bientôt,