OOP 2009 Anmerkungen (Teil 1)

Wie versprochen hier der erste Teil meiner Anmerkungen zu der auf der OOP 2009 gezeigten Beispielapplikation.

Ein Feature der Composite Application Library, das ich zwar auf einer Slide aber nicht im Vortrag selbst erwähnt habe, ist die Möglichkeit, die Callbacks für vom Event Agregator ausgelöste Ereignisse auf dem UI-Thread ausführen zu lassen. Warum ist das nützlich und wichtig? Weil auch für WPF die goldene Regel gilt, dass ein Steuerelement nur auf dem (UI-)Thread, auf dem es erzeugt wurde, manipuliert werden darf. 

Natürlich kann man das sogenannte Thread-Marshaling zur Umleitung der Ausführung auf den UI-Thread selbst realisieren, z.B. mittels System.Windows.Threading.Dispatcher. Dies jedoch für jeden Event Aggregator-Callback zu implementieren oder an anderer Stelle in die eigene Infrastruktur zu integrerieren ist eine eher mühselige und fehlerträchtige Fleißarbeit. Stattdessen kann man bereits bei der Registrierung eines Callback beim Event Aggregator angeben, auf welchem Thread dieser Callback später ausgeführt werden soll. Die CAL-Infrastruktur kümmert sich dann selbst um das Thread-Marshaling. Daher liegt es nah, jede Art von Callback-Registrierung in einem Presentation Model wie folgt durchzuführen:

 // Für Dateiänderungen in der Shell registrieren.
FileChangedEvent fileChangedEvent = EventAggregator.GetEvent<FileChangedEvent>();
fileChangedEvent.Subscribe(OnFileChanged, ThreadOption.UIThread);

Das klingt so weit alles gut und ist natürlich extrem nützlich. Wo also ist das Problem?

Je nachdem, ob man Test-Driven Development praktiziert oder nicht stößt man mehr oder weniger schnell darauf, dass (natürlich) das CAL-interne Thread-Marshaling ebenfalls auf den WPF-Dispatcher zurückgreift. Und genau der existiert in einem gewöhnlichen Test Fixture nicht. Man sollte also niemals in einem Presentation Model oder vergleichbaren Objekten der Präsentationsschicht (Presenter, Controller etc.) blind Callbacks auf dem UI-Thread registrieren, sondern erst prüfen, in welchem Kontext das Objekt ausgeführt wird, z.B. so:

 ThreadOption threadOption = 
    (Application.Current != null ? ThreadOption.UIThread : ThreadOption.PublisherThread);
fileChangedEvent.Subscribe(OnFileChanged, threadOption);

Durch die Option ThreadOption.PublisherThread sorgt CAL dafür, dass der Callback zur Ereignis-Behandlung auf dem Thread ausgeführt wird, auf dem das Ereignis publiziert wurde. In einem single-threaded ausgeführten Unit Test ist dies genau das gewünschte Verhalten (als habe man ein gewöhnliches C#-Event verwendet).

Übrigens funktioniert auch in der Beispiel-Applikation die Option ThreadOption.PublisherThread, da diese ebenfalls single-threaded ist – und der einzige Thread ist der UI-Thread. In einer realistischen WPF-Anwendung wird man aber üblicherweise mit Worker-Threads o.ä. arbeiten, um das UI nicht zu blockieren. Dann ist der oben gezeigte Ansatz nützlich, um die zuvor hart erarbeitete Testbarkeit nicht zu torpedieren.

 

Jörg