Dieses Blog durchsuchen

Dienstag, 11. Mai 2010

Die Mittelmänner entwickeln sich!

Ich bin ja so glücklich über das, was ich heute erreicht habe! Aber schön der Reihe nach:

Die Mittelmänner entwickeln sich weiter!

Wie kann man das verstehen? Ganz einfach: Weißt Du noch, als ich in meinen Präsentationen (z.B. diese hier) gesagt habe, dass die Mittelmänner selber von Außen keine Zugriffsmöglichkeiten haben (sollen)?
Ich habe heute meine Meinung geändert! (Ich habe aber auch nie gesagt, dass die Micro-Pattern-Trilogie bereits vollendet ist.) ^_^

Ich bin bei Ralf Westphals Blog über diesen Beitrag gestolpert: Asynchrone Kommunikation mit EBCs statt “Async-Pattern”

Ralf hat da absolut Recht, wenn er sagt, dass die Dinge, wie z.B. Threads, nix in der Businesslogik zu suchen haben! Ich hatte aber auch am Anfang den Fehler gemacht, als ich mal diesbezüglich experimentiert hatte. Ich hatte aber auch keine Idee, wie ich es denn am besten anders anstellen könnte.

Und heute grübelte ich etwas über diesen Fakt nach. Die natürliche Trennung zwischen den Objekten stellen ja die Mittelmänner dar. Also ist es doch naheliegend, wenn man an diesem Connector festlegt, ob der Nachrichtenfluss asynchron oder synchron verlaufen soll und nicht in den Businessmodellen!

Guter Stil ist es ja, wenn man ein Framework so entwirft, wie man es später auch auf einfache Weise verwenden möchte. So kam ich auf folgende Idee:

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace NewAsyncTest
{
 static class Program
 {
  /// <summary>
  /// Der Haupteinstiegspunkt für die Anwendung.
  /// </summary>
  [STAThread]
  static void Main()
  {
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);

   Form1 form1 = new Form1();
   BerechneModel berechneModel = new BerechneModel();

   new BerechneMediator(
    form1,
    berechneModel).Asynchronize();

   new ErgebnisFrequency(
    berechneModel,
    form1).Synchronize();

   Application.Run(form1);
  }
 }
}

Hier nochmal die relevante Stelle:

new BerechneMediator(
 form1,
 berechneModel).Asynchronize();

new ErgebnisFrequency(
 berechneModel,
 form1).Synchronize();

Wie man sieht, haben die Mittelmänner nun doch noch öffentliche Methoden bekommen.
Die "form1" ist der Auslöser für den "berechne"-Vorgang. Das "berechneModel" führt die Berechnung durch. Je nachdem, wie umfangreich die Berechnung ist, kann dies eine gewisse Zeit dauern. Deshalb wollen wir diesen Vorgang asynchron durchführen. Das hat zur Folge, dass der Nutzer in der Zwischenzeit ungehindert weiter mit der Anwendung arbeiten kann.

Wenn das "berechneModel" fertig ist, wird das Ergebnis einfach an die Form zurück gesendet. Aber Vorsicht! Es muss synchronisiert werden, weil die "form1" ein GUI ist. Tut man dies nicht, wird eine Exception geworfen!
Mit dem Methodenaufruf "Synchronize" ist das Thema aber schon gegessen.
So einfach kann die asynchrone/synchrone Nachrichtenverarbeitung sein. Und übersichtlich ist das ganze auch noch.
Ist das geil, oder ist das geil? ^_^

Und wie sieht die Sache intern aus?
Der Übersichtlichkeit halber, zeige ich hier nur mal den "BerechneMediator":

using System;
using System.Collections.Generic;
using System.Text;

namespace NewAsyncTest
{
 public interface IAmBerechneCaller
 {
  event EventHandler Berechne;
 }

 public interface IAmBerechneListener
 {
  void Berechne();
 }

 public class BerechneMediator : Middleman
 {
  private readonly IAmBerechneCaller caller;
  private readonly IAmBerechneListener listener;

  public BerechneMediator(
   IAmBerechneCaller caller,
   IAmBerechneListener listener)
  {
   this.caller = caller;
   this.listener = listener;

   this.caller.Berechne += Do(caller_Berechne);
  }

  void caller_Berechne(object sender, EventArgs e)
  {
   listener.Berechne();
  }
 }
}

Kein Witz! So sieht das wirklich aus. Mein Ziel war es ja, dass ich meine Pattern genauso weiterverwenden kann, wie ich es bisher getan habe.
Man sieht erst auf den zweiten Blick, was sich geändert hat. JEDER Mittelmann muss ab sofort von der abstrakten Klasse "Middleman" erben, wenn er die komfortablen synch/asynch Features nutzen will.
Wenn er diese geerbt hat, muss er beim Zuweisen des Event-Handlers die geschützte "Do"-Methode verwenden. Mehr ist hierbei absolut nicht zu tun.

Sehen wir uns nun die abstrakte Basisklasse "Middleman" an. Denn nur dort befindet sich die gesamte Infrastruktur.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.ComponentModel;

namespace NewAsyncTest
{
 public abstract class Middleman
 {
  private bool isAsynchronizer = false;
  private bool isSynchronizer = false;
  private AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

  public void Asynchronize()
  {
   isSynchronizer = false;
   isAsynchronizer = true;
  }

  public void Synchronize()
  {
   isSynchronizer = true;
   isAsynchronizer = false;
  }

  public void NormalMode()
  {
   isSynchronizer = false;
   isAsynchronizer = false;
  }

  protected EventHandler Do(EventHandler customMethod)
  {
   return new EventHandler(
    delegate
    {
     if (isAsynchronizer)
     {
      ThreadPool.QueueUserWorkItem(delegate { customMethod(this, EventArgs.Empty); });
     }
     else if (isSynchronizer)
     {
      asyncOp.Post(new SendOrPostCallback(
       delegate
       {
        customMethod(this, EventArgs.Empty);
       }), null);
     }
     else
     {
      customMethod(this, EventArgs.Empty);
     }
    });
  }
 }
}

Sieht kompliziert aus, ist es aber gar nicht. ;-)
Es sind die anonymen delegates, die das Ganze etwas wüst aussehen lassen. *lach*
Über die 3 öffentlichen Methoden werden lediglich Statusflags gesetzt. (Ja, es sind 3 und nicht 2 Methoden, weil ich noch eine Methode wollte, mit der ich die Flags wieder zurücksetzen kann.)

In der geschützten "Do" Methode wird lediglich ein neuer EventHandler zurückgegeben. Und in diesem Handler steckt eine Kontrollanweisung, die einfach prüft, welche Rolle der "Mittelmann" im Moment spielt. Ist er ein "Asynchronizer", wird die "customMethod" einfach in den ThreadPool gesteckt. Und nach mir die Sintflut. *g*
Ist er ein "Synchronizer", wird die "customMethod" in die "asyncOp" gesteckt. (Dieses Ding sorgt voll automatisch dafür, dass ein beliebiger Thread mit dem GUI-Thread synchronisiert wird.)
Wenn er letztlich nix von Beiden ist, dann soll die "customMethod" ganz normal aufgerufen werden.

Und weil diese Prüfung bei JEDEM ausgelösten Event durchgeführt wird, könnte man quasi zur Laufzeit der Anwendung das Verhalten ändern.

Also ich finde das richtig geil! Da hier in der grundlegenden Struktur absolut nicht viel geändert werden muss, habe ich das heute auch in sehr kurzer Zeit in meinem Softwareprojekt auf Arbeit aktualisiert. Da habe ich nämlich auch einen Task, der am besten asynchron ablaufen müsste. Das ist jetzt ein Kinderspiel! :-D
Es ist so geil! ^_^

Das soll es auch schon gewesen sein.
Ich habe hier auch ein Visual Studio 2008 Projekt, wo Du das mal ausprobieren kannst: KLICK [rar-Archiv]

Keine Kommentare:

Kommentar veröffentlichen