Montag, 31. Januar 2011

Asynchrone eventbasierte Programme mit Rx

Liebe Leser,

am 12. Oktober 2010 hatte ich das Vergnügen mich beim Mittagessen mit Bart de Smet (Microsoft) über Rx für .NET [1] zu unterhalten. Zuvor konnte ich seiner amüsant inszenierten hoch professionellen Keynote beim DevCamp 2010 [2] beiwohnen. Rx steht für Reactive Extensions for .NET und kann mit einem Satz folgender Maßen beschrieben werden:

Rx ist eine Bibliothek zur Erstellung von asynchronen und event-basierten Programmen, welche "observable collections" verwenden.

Das mag auf den ersten Blick nicht sonderlich spannend klingen. Denkt man allerdings ein wenig weiter, dann offenbaren sich tolle Möglichkeiten um kostengünstigere, wartungsfreundlichere und für den Benutzer bedienungsfreundlichere Software zu erstellen.

Viele sind vermutlich schon mindestens einmal vor dem Szenario gestanden, dass auf Grund einer Eingabe von Daten oder einer sonstigen Benutzeraktion eine Liste von Ergebnissen angefordert wird, welche dann in Form eines Listen- oder Gridsteuerelementes angezeigt werden soll. In der Regel muss man als Entwickler die Entscheidung treffen, ob man das Laden der Daten synchron oder asynchron umsetzt. Der einfachere Weg ist sicher die synchrone Verarbeitung, welche sich in einem nicht reaktionsfähigen GUI auswirkt, dafür aber viel einfacher und übersichtlicher programmierbar ist. Für kleine Datenmengen und schnelle Zugriffsmöglichkeiten auf Daten ist dies sicher ein gültiger Ansatz. Jedoch stößt man in der Praxis leider oft auf andere Gegebenheiten, wo man evtl. ein Service konsumieren muss, welches die Daten liefert. Für viele Applikationen ist es ein No-go, wenn das GUI in einen blockierenden Zustand gerät. Bleibt also noch die asynchrone Variante. Wer sich schon einmal dafür entschieden hat, dem ist bewusst, dass hierfür zwar Methoden in .NET zur Verfügung stehen, jedoch einiges an Mehraufwand notwendig ist und der produzierte Sourcecode auch nicht sonderlich wartungsfreundlich wirkt. Ich wage auch zu behaupten, dass manche Konstellationen sehr schwer damit umsetzbar sind.

Hier kommt Rx ins Spiel. Rx bietet elegante und einfach anzuwendende Möglichkeiten um eine asynchrone Verarbeitung von event-gesteuerten Aktionen zu realisieren. Es ermöglicht weitergehend die Kombination von asynchronen Verarbeitungen ohne unnötigen Verbindungscode an verteilten Stellen im Sourcecode schreiben zu müssen (Stichwort Wartbarkeit!). Die asynchronen Verarbeitungen werden als Datenquellen betrachtet, wodurch das LINQ Programmiermodell zur Anwendung kommt. Man kann sich beispielsweise Mausbewegungen einfach als Datenbank von Punkten vorstellen. Rx bietet ein ein push-based Modell im Gegensatz zu dem pull-based Ansatz, der bis dato häufig vorherrscht.


Wie verwendet man nun Rx und was ist als Voraussetzung notwendig? Die beiden wichtigsten Interfaces wurden bereits mit der .NET 4 Base Class Library (BCL) mitgeliefert. Dabei handelt es sich um IObservable<T> und IObserver<T>. Damit ist lediglich die Basis gelegt. Für den effizienten Einsatz muss man eine der Rx Bibliotheken herunterladen und einbinden. Rx steht aktuell für .NET 4, .NET 3.5 SP1, Silverlight 3, Silverlight 4 und JavaScript zur Verfügung. Des Weiteren ist Rx integraler Bestandteil von Windows Phone 7. Alle Ausprägungen sind auf der Rx Downloadseite verfügbar.

Die folgenden Codefragmente sollen den Unterschied zwischen synchroner und asynchroner Verarbeitung von Collections verdeutlichen.

Gewohnte Verarbeitung von Collections:

IEnumerable<BlogItem> items = _service.GetBlogItems();
foreach (BlogItem item in items)
{
    // Verarbeitung der BlogItem Objekte...
}

// Erst wenn alle BlogItems geliefert wurden, kommt man an diese Stelle.

Mittels Rx und den ObservableCollections würde der Code wie folgt aussehen:

IObservable<BlogItem> items = _service.GetBlogItems();
items.Subscribe(item =>
{
    // Verarbeitung der BlogItem Objekte...
});

// Sofort hier. Sobald Daten ankommen -> Lambda Expression aufgerufen.

Rx baut auf der LINQ Infrastruktur auf und erlaubt uns daher einen sehr komfortablen Umgang mit den asynchron zu liefernden Daten. Zum Beispiel können Einschränkungen sehr einfach mittels „where“ eingebaut werden.

Die bereits erwähnte Überlegung mit den Mousekoordinaten soll für ein weiteres Beispiel noch einmal aufgegriffen werden. Der MouseMove Event wird einer Form zugefügt und gleichzeitig mittels "observable collection" abgefragt. Folglich wird die Mouse lediglich als Datenquelle von Punkten betrachtet:

var form = new Form();
var mouseMoves = from mme in Observable.FromEvent<MouseEventArgs>(form, "MouseMove")
                 let point = mme.EventArgs.Location
                 where point.X == point.Y
                 select point;

using (mouseMoves.Subscribe(pt =>
       {
          Console.WriteLine(“Mouse Cursor bei {0}”, pt);
       })
{
   Application.Run(form);
}

Im obigen Beispiel wird mittels LINQ Query die Eventsequenz zu einer Sequenz aus Punkten (Point Objekt) projiziert und gleichzeitig nach Gleichheit von X- und Y-Koordinate gefiltert.

Rx soll nicht als Ersatz für bestehende Möglichkeiten der eventgesteuerten (asynchronen) Programmierung dienen. Durch die Verwendung von observable Collections entstehen jedoch einfacher und übersichtlicher zu entwickelnde Anwendungsszenarien, die bisher nur mit Mühe zu bewerkstelligen waren. Um die vielfältigen Möglichkeiten von Rx kennenzulernen empfiehlt sich ein Blick auf die Rx Beispielsammlung [3].

Euer
JL@coopXarch

[1] Rx Webseite (DevLabs) http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
[2] DevCamp http://www.devcamp.at
[3] 101 Rx Samples http://rxwiki.wikidot.com/101samples