Dojos für Entwickler 2. Stefan Lieser

Чтение книги онлайн.

Читать онлайн книгу Dojos für Entwickler 2 - Stefan Lieser страница 11

Автор:
Серия:
Издательство:
Dojos für Entwickler 2 - Stefan Lieser

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

den Rahmen der übung sprengen. Für die Praxis ist das kein Beinbruch, da die Synchronisierung in aller Regel im Zusammenhang mit dem UI auftritt. Genau in diesem Szenario funktionieren SynchronizationContext bzw. Dispatcher bei WPF und Silverlight wunderbar.

      Doch wie testet man nun den Synchronizer? Ich sehe hier im Wesentlichen zwei Aspekte, die durch automatisierte Tests sichergestellt werden sollten :

       Zum einen muss sichergestellt sein, dass der Eingangsparameter der Process-Methode an den Result-Event übergeben wird. Der Flow darf schließlich nicht unterbrochen werden.

       Zum anderen sollte ein Test sicherstellen, dass der Eventhandler, der durch den Result-Event ausgelöst wird, auf dem korrekten Thread abläuft.

      Für den zweiten Test ist zu berücksichtigen, dass dies nur im Rahmen einer Windows-Forms-Anwendung funktioniert. Der erste Test für den Synchronizer erfolgt analog zum Test des Asynchronizers: Die Process-Methode wird mit einem Parameter aufgerufen, um anschließend zu überprüfen, ob der Result-Event den Parameter wieder mitführt, siehe Listing 7.

      Listing 7

      Den Synchronizer testen.

      [TestFixture] public class SynchronizerTests { private Synchronizer<int> sut; private int result; [SetUp] public void Setup() { sut = new Synchronizer<int>(); sut.Result += x => result = x; } [Test] public void Parameter_wird_durchgereicht() { sut.Process(42); Assert.That(result, Is.EqualTo(42)); } )

      Da durch den Synchronizer kein weiterer Thread gestartet wird, der Synchronizer arbeitet synchron, muss im Test nicht auf die Ausführung eines anderen Threads mittels WaitHandle gewartet werden, wie das beim Asynchronizer der Fall war. Dieser Test war also ganz einfach.

      Trickreicher Test

      Lange beschäftigt hat mich dagegen die Frage, wie man automatisiert testet, ob der Synchronizer in einer Windows- Forms-Anwendung tatsächlich den Eventhandler auf dem UI-Thread ausführt. Dazu müssen zwei Voraussetzungen geschaffen werden:

       Der Synchronizer darf erst instanziert werden, nachdem eine Windows- Forms-Form instanziert wurde. Andernfalls wird dem Vordergrundthread kein WindowsFormsSynchronization- Context hinzugefügt.

       Die Windows-Forms-Message-Loop muss laufen. Dazu muss Application.Run aufgerufen werden.

      Der Aufruf von Application.Run in Listing 8 erfolgt synchron, das heißt, er kehrt erst zum Aufrufer zurück, wenn die „Awendung“ beendet wird. Um die „Anwendung“ zu beenden, ist es erforderlich, die an Application.Run übergebene Form zu schließen. Ich rufe dazu in der Lambda- Expression des Result-Events, nach dem überprüfen der Thread-ID, die Close- Methode der Form auf. Dadurch wird Application.Run verlassen. Das ist ein etwas trickreicher Test, der einige Annahmen über das Verhalten von Windows Forms trifft. Aber genau dieses Szenario soll hier getestet werden, insofern bin ich mit dem Test zufrieden.

      Listing 8

      Test für Windows Forms.

      [Test] public void Result_Event_wird_auf_Zielthread_ausgeführt_wenn_das_Ziel_WinForms_ist() { var myForm = new Form(); sut = new Synchronizer<int>(); var mainThreadId = Thread.CurrentThread. ManagedThreadId; sut.Result += _ => { Assert.That(Thread.CurrentThread. ManagedThreadId, Is.EqualTo(mainThreadId)); myForm.Close(); }; var thread = new Thread(() => sut.Process(1)); thread.Start(); Application.Run(myForm); }

      Scatter/Gather

      Nun zum zweiten Teil der übung. Häufig lassen sich Aufgaben schneller erledigen, wenn mehrere Elemente gleichzeitig bearbeitet werden. Zu einer echten Beschleunigung kommt es nur, wenn mehrere Kerne des Prozessors ausgenutzt werden können. Andernfalls kommt es sogar zu Verzögerungen, da das Wechseln von einem zum anderen Thread einen gewissen Overhead mit sich bringt.

      Das Szenario für den Einsatz der Scatter- und Gather-Bausteine sieht so aus, dass in einer Liste mehrere Elemente zur Verfügung stehen, die von zwei parallel ausgeführten Bausteinen bearbeitet werden sollen. Scatter entnimmt also Elemente aus der Liste und reicht sie jeweils an den einen oder anderen Baustein weiter. Natürlich erfolgt die Weitergabe der Elemente auf einem anderen Thread, sodass die beiden Ausgänge des Scatter- Bausteins auf je einem eigenen Thread laufen.

      Damit man diese Form der Parallelisierung zu einem späteren Zeitpunkt so einfach wie möglich in einen Flow integrieren kann, soll das API von Scatter und Gather ebenfalls auf Aufzählungen IEnumerable<T> arbeiten. So kann ein Baustein, der ursprünglich für die Bearbeitung aller Elemente zuständig war, unverändert mehrfach instanziert werden und mit je einem Ausgang des Scatter- Bausteins verbunden werden. Würde der Scatter-Baustein jeweils ein zu bearbeitendes Element am Ausgang zur Verfügung stellen statt eine Aufzählung, dann müsste der Bearbeitungsbaustein angepasst werden.

      Die folgenden Codeausschnitte zeigen, wie die Scatter- und Gather-Bausteine verwendet werden. Listing 9 zeigt, wie die Logik für das Ermitteln von Stichwörtern aus JPEG-Dateien zusammengesteckt wird, ohne Parallelisierung oder Asynchronizität.

      Listing 9

      Stichwörter ermitteln, einfache Version.

      public class Eindeutige_Stichwörter_ ermitteln { private readonly Action<Tuple<string, string>> process; public Eindeutige_Stichwörter_ermitteln() { var dateinamen_suchen = new Dateinamen_suchen(); var alle_Stichwörter_ermitteln = new Alle_Stichwörter_ermitteln(); var eindeutige_Stichwörter_filtern = new Eindeutige_Stichwörter_filtern(); dateinamen_suchen.Result += alle_Stichwörter_ermitteln.Process; alle_Stichwörter_ermitteln.Result += eindeutige_Stichwörter_ filtern.Process; eindeutige_Stichwörter_ filtern.Result += x => Result(x); process = path_und_SearchPattern => dateinamen_suchen.Process( path_und_SearchPattern); } public void Process(Tuple<string, string> path_und_SearchPattern) { process(path_und_SearchPattern); } public event Action<IEnumerable<string>> Result; }

      In diese Platine werden nun Scatter- und Gather-Bausteine eingebracht, um das Auslesen der Stichwörter zu parallelisieren, siehe Listing 10. Dazu wird der Baustein Alle_Stichwörter_ermitteln zweimal instanziert. Der Baustein erwartet eine Liste von Dateinamen am Eingang und liefert dazu eine Liste aller gefundenen Stichwörter am Ausgang. Das Ermitteln der Dateinamen wird vom Baustein Dateinamen_suchen übernommen. Für die Parallelisierung der Aufgabe wird daher der Ausgang von Dateinamen_suchen mit dem Eingang eines Scatter-Bausteins verbunden. Dieser sorgt dafür, dass die Eingangsdaten auf die beiden Ausgänge verteilt werden. An die beiden Ausgänge des Scatter-Bausteins werden daher die beiden Alle_Stichwörter_ermitteln-Bausteine gehängt. Um die Ergebnisse wieder zusammenzuführen, wird jeweils der Ausgang von Alle_Stichwörter_ermitteln mit einem Eingang eines Gather-Bausteins verbunden. Dieser sammelt die Ergebnisse und stellt sie an seinem Ausgang zur Verfügung. Abbildung 1 zeigt den Flow vor, Abbildung 2 nach der Parallelisierung.

figure3d

      [Abb. 1]

       Vor der Parallelisierung ...

figure3c

      [Abb.

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