Stefan Lieser

Dojos für Entwickler 2


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

und einen AutoResetEvent gesetzt. Doch dabei kam es sporadisch dazu, dass der Baustein blockierte. Die Synchronisierung der beiden Eingänge ist nicht so trivial, wie es zunächst den Anschein haben mag. Doch das .NET Framework hält eine weitere Datenstruktur bereit, die in diesem Fall die Lösung bedeutete: eine BlockingCollection. Die bewerkstelligt genau, was ich gebraucht habe: Auf der Eingangsseite sollen Daten in die BlockingCollection ergänzt werden können. Mit der Add-Methode ist das kein Problem. Und da diese Datenstruktur threadsicher ist, können auch beide Eingänge asynchron Einträge ergänzen, ohne dass es zu Problemen kommt.

      Auf der Ausgangsseite liegt die Herausforderung. Hier können natürlich nur Elemente entnommen werden, wenn welche vorhanden sind. Wenn kein Eintrag vorhanden ist, muss der Thread, auf dem der Ausgangsevent des Gather-Bausteins läuft, angehalten werden. Erst wenn auf der Eingangsseite wieder ein Element ergänzt wird, soll der Thread weiterlaufen. Thread.Sleep kommt nicht infrage, so viel dürfte inzwischen klar sein. Ursprünglich hatte ich hier den AutoResetEvent verwendet, um auf neue Daten zu warten. Doch die BlockingCollection macht genau, was ich brauche: Sie blockiert, wenn keine Daten anliegen. Man kann sie daher gefahrlos iterieren. Im MoveNext des zugrunde liegenden Enumerators wird der Thread so lange angehalten, bis Daten anliegen. Damit sieht meine Implementation des Gather-Bausteins so wie in Listing 13 aus.

      Listing 13

      Der Gather-Baustein.

      public class Gather<T> { private bool resultWasRaised; private readonly BlockingCollection<T> queue = new BlockingCollection<T>(); private bool intpu1IsEmpty; private bool intpu2IsEmpty; public void Input1(IEnumerable<T> input) { RaiseResultEvent(); Iterate(input); intpu1IsEmpty = true; if(intpu1IsEmpty && intpu2IsEmpty) { queue.CompleteAdding(); } } } public void Input2(IEnumerable<T> input) { RaiseResultEvent(); Iterate(input); intpu2IsEmpty = true; if (intpu1IsEmpty && intpu2IsEmpty) { queue.CompleteAdding(); } } private void Iterate(IEnumerable<T> input) { foreach (var t in input) { queue.Add(t); } } private void RaiseResultEvent() { if (resultWasRaised) { return; } resultWasRaised = true; var thread = new Thread(x => Result(EnumerateTheQueue())); thread.Start(); } private IEnumerable<T> EnumerateTheQueue() { foreach (var item in queue.GetConsumingEnumerable()) { yield return item; } } public event Action<IEnumerable<T>> Result; }

      Der Haken an der Sache

      Es sei gleich erwähnt, dass die Implementation einen Haken hat: Der Flow kann nicht mehrfach durch den Gather- Baustein fließen. Die beiden Inputs können beide nur genau einmal aufgerufen werden. Beim zweiten Aufruf ist die BlockingCollection im Zustand AddingCompleted. Die Lösung dieses Problems ist deutlich aufwendiger, als es zunächst erscheinen mag. Prinzipiell müsste die BlockingCollection für jeden neuen Satz von Eingangsdaten erzeugt werden. Doch die Input1- und Input2-Aufrufe müssten korreliert werden: Es muss erkannt werden, welche Aufrufe zusammengehören. Wenn beispielsweise Input1 erneut aufgerufen wird, darf die BlockingCollection erst neu angelegt werden, wenn die bisherige abgearbeitet ist.

      Das gleiche Problem stellt sich übrigens auch bei der asynchronen Verwendung des Join-Bausteins aus dem ebclang-Projekt [1]. Auch hier muss bei asynchroner Verwendung eine Korrelation und eine Eingangswarteschlange für die Inputs verwendet werden.

      Diese Herausforderung geht deutlich über den Rahmen einer übung hinaus. Nichtsdestoweniger mag sich der geneigte Leser an dieser Herausforderung versuchen. Die asynchrone Programmierung wird mit steigender Zahl von Prozessorkernen einen größeren Stellenwert einnehmen. Da lohnt es, sich beizeiten damit zu befassen.

      Fazit

      Die ersten Schritte in Richtung Parallelisierung, Multithreading und Asynchronizität sind schnell getan. Dank leistungsfähiger Datenstrukturen im .NET Framework müssen nicht alle Probleme selbst gelöst werden. Doch es bleiben auch zahlreiche Herausforderungen offen, die man als Softwareentwickler beherrschen sollte. Auch wenn das Ziel darin besteht, die Asynchronizität im Sinne eines Aspekts aus der Kernlogik einer Anwendung herauszuhalten, muss man sich damit auseinandersetzen. In den nächsten Ausgaben der dotnetpro werden Sie Weiteres zu diesem Thema lesen können.

       [ml]

      Aufgabe 4

      Screen-Scraping für Webanwendungen

      Wann kommt der Bus?

      Viele Websites bieten ihre Daten und Services auch über eine Programmierschnittstelle (API) an. Wenn eine solche Schnittstelle nicht existiert, bleibt als Ausweg nur Screen-Scraping.

      Die Idee beim Scraping besteht darin, die Webanwendung wie ein Browser über HTTP anzusprechen. Aus dem gelieferten HTML-Datenstrom liest man die benötigten Informationen heraus. Das Verfahren wird allgemein als Screen-Scraping bezeichnet. Ich kann mich noch an die „Green Screen“-Zeiten erinnern. Da hingen beispielsweise IBM-5250-Terminals an einer AS/400. Als die Windows-PCs in Mode kamen, konnte man 5250-Emulatoren unter Windows einsetzen, um aus dem 5250-Terminaldatenstrom Informationen herauszulesen. So wurde eine Bedienung der AS/400-Anwendungen mit der Maus unter Windows möglich. Über die Sinnhaftigkeit lässt sich streiten.

      Heute spielen Terminals keine Rolle mehr. Aber eine vergleichbare Aufgabenstellung ergibt sich häufig für Webanwendungen. Existiert kein API, über das die Webanwendung angesprochen werden kann, bleibt nur der Ausweg über HTTP und HTML. Ein konkretes Beispiel, an dem ich zurzeit arbeite: Die Kölner Verkehrsbetriebe (KVB) stellen die aktuellen Abfahrtszeiten von Bussen und Bahnen zu fast allen Haltestellen im Web zur Verfügung [1]. An den Haltestellen befinden sich QR-Codes, die beim Scannen auf die jeweilige Webseite der Haltestelle führen, siehe Abbildung 1.

figure4a1

      [Abb. 1]

       Der KVB-Fahrplan im Browser des iPhones ..

      Das ist eine tolle Sache! Allerdings habe ich mir eine App für mein iPhone gewünscht, in der ich die Haltestelle auswählen kann und dann die gleichen Informationen angezeigt bekomme. Eine solche App gab es nicht, also musste ich sie mir selbst bauen, siehe Abbildung 2.

figure4a2

      [Abb. 2]

       ... und als native iPhone-App.

      Die Daten werden von den KVB zurzeit nur in Form von HTML zur Verfügung gestellt, also blieb mir nichts anderes übrig, als die Daten aus dem HTML-Dokument auszulesen.

      Glücklicherweise muss man für Web-Scraping nicht alles selbst bauen. Es gibt die Open-Source-.NET-Bibliothek Html Agility Pack [2]. Damit ist das Auslesen einzelner HTML-Elemente sehr komfortabel über XPath-Ausdrücke möglich. Und mit dem WebClient aus dem .NET Framework kann der HTML-Datenstrom besorgt werden. Das funktioniert auch asynchron.

      Die Übung für diesen Monat besteht darin, mittels WebClient und Html Agility Pack eine Anwendung zu bauen, die asynchron Daten aus dem Web lädt und diese auf der Konsole oder in einem GUI anzeigt. Was Sie als Datenquelle nehmen, bleibt Ihnen überlassen. Folgende Beispiele mögen Ihnen als Anregung dienen:

       Suchen eines Buches bei Amazon über den Titel oder die ISBN, um herauszufinden, ob es das Buch als E-Book für den Kindle gibt [3].

       Anzeigen der aktuellen Abfahrten der Bahn AG an einem einzugebenden Bahnhof [4].

       Anzeigen der aktuellen Abfahrtszeiten an einer KVB-Haltestelle [5].

      Das Laden der HTML-Daten sollte in jedem Fall asynchron erfolgen. Andernfalls friert die Benutzerschnittstelle ein, das ist heutzutage nicht mehr zu entschuldigen. Experimentieren Sie mit der Frage, ob die HTML-Daten mit dem Html Agility Pack im Hintergrund verarbeitet werden sollten oder ob dies im Hauptthread passieren kann. Happy Scraping!

      [1] KVB,