Stefan Lieser

Dojos für Entwickler 2


Скачать книгу

ob das args-Array passend gefüllt ist.

      An den Result-Event der Platine wird eine Lambda-Expression gebunden, welche die ermittelten Stichwörter mit Console.Write­­Line auf der Konsole ausgibt. Hier stellt sich natürlich die Frage, ob es tatsächlich die Aufgabe der Anwendung sein sollte, sich um die Konsolenausgabe zu kümmern. Zumindest die Main-Methode sollte sich damit nicht unmittelbar befassen. Daher habe ich die Konsolenausgabe in das Bauteil Auf_Konsole_aus­geben ausgelagert, siehe Listing 13. Damit reduziert sich die Main-Methode auf den Code in Listing 14.

      Listing 13

      Konsolenausgabe auslagern.

      public class Auf_Konsole_ausgeben { public void Process( IEnumerable<string> zeilen) { foreach (var zeile in zeilen) { Console.WriteLine(zeile); } } }

      Listing 14

      Das Hauptprogramm.

      public static class Program { public static void Main(string[] args) { var eindeutige_Stichwörter_Ermitteln = new Eindeutige_Stichwörter_ermitteln(); var auf_Konsole_ausgeben = new Auf_Konsole_ausgeben(); eindeutige_Stichwörter_Ermitteln.Result += auf_Konsole_ausgeben.Process; eindeutige_Stichwörter_Ermitteln.Process( new Tuple<string, string>( args[0], args[1])); } }

      Fazit

      Die Aufgabe hat in der Vorbereitung mehr Zeit in Anspruch genommen, als ich vermutet hatte. Das lag daran, dass die Softwarehersteller sich dazu entschlossen haben, die Stichwörter nicht gleich in den JPEG-Dateien abzulegen. Die Herausforderung, die Hierarchie in den Stichworten darzustellen, konnte ich gar nicht meistern, da diese Information beim Export aus dem Elements Organizer verloren geht. Doch keine Sorge, es wird eine weitere Iteration des Programms geben. Schauen Sie sich einfach die neue Aufgabe in diesem Heft an!

       [ml]

      [1] NUnit, www.nunit.org [2] ReSharper, www.jetbrains.com/resharper

      Aufgabe 3

      Bilddateien parallel verarbeiten

      Alle anpacken!

      Sie sollen aus vielen JPEG-Dateien die Stichworte extrahieren. Eine ideale Aufgabenstellung für Parallelverarbeitung. Aber läuft das Programm damit auch wirklich schneller?

      Versuch macht klug. Daher soll es in der Aufgabe für diesen Monat darum gehen herauszufinden, ob die Parallelverarbeitung von Dateien das Programm beschleunigen kann. Natürlich soll dabei Flow-Design zum Einsatz kommen. Denn Flow-Design hat im Bereich der Parallelisierung einen entscheidenden Vorteil : Ein Entwurf, der zunächst ohne den Aspekt der Parallelisierung erstellt wurde, kann leicht um Parallelverarbeitung ergänzt werden. Das gilt natürlich nur, wenn überhaupt einzelne Schritte im Flow parallel ausführbar sind, ohne dass dadurch die Semantik verändert wird. Bei der Parallelisierung von Flows lassen sich zwei Fälle unterscheiden:

       Es existieren im Flow bereits parallele Datenflüsse, die allerdings bislang sequenziell ausgeführt werden.

       Es werden Aufzählungen von Daten verarbeitet, sodass sich eventuell mehrere Instanzen parallel um die Verarbeitung kümmern können.

      Abbildung 1 zeigt den ersten Fall vor und nach der Parallelisierung.

      Damit die beiden Pfade auf jeweils eigenen Threads parallel ausgeführt werden, muss jeweils zu Beginn und Ende des Pfades eine zusätzliche Funktionseinheit für den Threadwechsel sorgen. Die Funktionseinheit Asynchronize sorgt dafür, dass auf einem neuen Thread weitergearbeitet wird. Am Ende des Teil-Flows sorgt Synchronize dafür, dass der Flow auf den ursprünglichen Thread zurückkehrt. Dies ist vor allem in Verbindung mit Windows Forms und WPF wichtig, weil ein Zugriff auf die Controls meist nur aus dem UI-Thread heraus gestattet ist. Ihre erste Aufgabe für diesen Monat besteht darin, die beiden Funktionseinheiten Asynchronize und Synchronize zu implementieren. Das ist mithilfe der Klassen Thread und SynchronizationContext eine überschaubare Herausforderung.

figure3a

      [Abb. 1]

       Parallele Pfade.

      In Abbildung 2 sehen Sie den zweiten Fall für eine mögliche Parallelisierung.

      Die Funktionseinheit A erhält mehrere Elemente vom Typ X und verarbeitet diese zu mehreren Elementen vom Typ Y. Der Stern zeigt an, dass statt eines einzelnen Datums eine Aufzählung von Daten fließt. Ziel ist es, die Bearbeitung der Aufzählung von mehreren parallel arbeitenden Instanzen der Funktionseinheit A ausführen zu lassen. Dazu verteilt der Scatter-Baustein die anstehenden Daten auf mehrere parallel arbeitende Funktionseinheiten. Die Ergebnisse sammelt Gather am Ende wieder ein und fasst sie zu einer Aufzählung zusammen. Um die Aufgabe nicht zu kompliziert zu machen, genügt es, Scatter und Gather mit einer fixen Anzahl von Ein- bzw. Ausgängen zu versehen. Beginnen Sie ruhig mit nur zwei Ein- bzw. Ausgängen.

      Natürlich müssen die Daten sich für eine Parallelverarbeitung eignen. Müssen die Daten etwa in einer vorgegebenen Reihenfolge bearbeitet werden, wird eine Parallelisierung schwierig. Durch die parallel laufenden Threads ist nämlich die Einhaltung der Reihenfolge nicht mehr automatisch sichergestellt. Müssen die Threads zur Einhaltung der Reihenfolge aufeinander warten, bringt die Parallelisierung aus Performancesicht ohnehin nichts. Und auch bei Daten, deren Bearbeitungsreihenfolge keine Rolle spielt, müssen Sie darauf achten, dass die Daten jeweils genau einmal bearbeitet werden. [ml]

figure3b

      [Abb. 2]

       Aufzählungen parallelisiert bearbeiten.

      Lösung 3

      Parallelisierung im Flow-Design

      Erst trennen, dann vereinen

      Mit Flow-Design lassen sich auch parallele Vorgänge modellieren. Standardbausteine erleichtern die Umsetzung. Diese Übung ergänzt die neuen Bausteine Scatter und Gather.

      Inzwischen sind in der dotnetpro über 20 dojo-Lösungen erschienen. In vielen davon habe ich eine Lösung mit Flow-Design modelliert und umgesetzt. Und in zahlreichen davon stand bereits der Hinweis auf das ebclang-Projekt [1]. Wer wollte, konnte dort eine Lösung für den ersten Teil der Parallelisierungsaufgabe finden: Asynchronizer und Synchronizer sind Standardbausteine, die dort implementiert sind. Aber natürlich haben Sie Ihre übungszeit ernst genommen und beide Bausteine selbst implementiert. Oder?

      Sollten Sie das ebclang-Projekt als „Spickzettel“ benutzt haben, werden Sie gleich merken, dass ich hier die Implementation aus genau diesem Projekt als Lösung der Aufgabe vorstellen werde. Das betrifft allerdings nur den ersten Teil der übung. Der zweite Teil, Scatter/Gather, befindet sich bislang nicht im ebclang-Projekt.

      Async/Sync

      Aufgabe des Asynchronizer-Bausteins ist es, den eingehenden Datenfluss auf einen anderen Thread umzuleiten. Der Asynchronizer hat dazu eine Process-Methode als Eingang und ein Result-Event als Ausgang. Die Kernidee beim Asynchronizer ist: Beim Aufruf der Process-Methode wird der Result-Event ausgelöst, allerdings auf einem anderen Thread. Und natürlich wird der Parameter der Process-Methode an den Result-Event weitergereicht, sodass der Datenfluss durch den Asynchronizer hindurchfließt. Soll ein Baustein den Datenfluss einfach nur durchreichen, sieht die Implementation so wie in Listing 1 aus.

      Listing 1

      Den Datenfluss durchreichen.

      public class Asynchronizer<T> { public void Process(T input) { Result(input);