Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Threadübergreifender Eventmechanismus (AsnycOperation)
parsifal
myCSharp.de - Member



Dabei seit:
Beiträge: 66
Herkunft: Siegen

Themenstarter:

Threadübergreifender Eventmechanismus (AsnycOperation)

beantworten | zitieren | melden

Beschreibung:

Der folgende Codeauszug soll verdeutlichen wie der Einsatz der Klasse AsyncOperation genutzt werden kann, um Controls im UI-Thread, durch ein Ereignis das in einem anderen Thread ausgelöst wurde, zu aktualisieren ohne auf delegates und die Abfrage der Eigenschaft InvokeRequired zurückzugreifen.

Ausgangsbasis ist eine Klasse 'EventService' die Ereignisse anbietet um über Änderungen an Daten zu informieren. Diese ist durch die Schnittstelle 'IEventService' definiert ist. Eine Instanz der Klasse 'EventService' befindet sich in einem anderen Thread (oder Prozess), als dem UI-Thread und bietet Ereignisse an, die z. B. dazu verwendet werden Controls im UI-Thread zu aktualisieren.


public interface IEventService {
  event EventHandler Changed;
}

public class EventService : IEventService {
  #region IEventService Members

  public event EventHandler Changed;

  #endregion

  public void ChangeSomething() {
    // Ändert irgendetwas und feuert dann das Ereignis Changed.
    ThrowChangedEvent( EventArgs.Empty );
  }

  protected virtual void ThrowChangedEvent( EventArgs e ) {
    EventHandler changed = Changed;
    if ( changed != null ) {
      changed( this, e );
    }
  }
}

Im o. g. Szenario würde man eine InvalidOperationException mit dem u. g. Text erhalten, wenn man z. B. versuchen würde in der EventHandler-Methode eine Textbox mit Text zu füllen.

Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement Form1
erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

Um diese threadübergreifenden Vorgänge zu umgehen, ist in der EventHandler-Methode die Eigenschaft InvokeRequired von Controls zu prüfen,
ob ein direkter oder ein indirekter Zugriff (mittels delegate und Invoke) erfolgen kann/muss.


private void OnEventServiceChanged( object sender, EventArgs e ) {
  if ( this.InvokeRequired ) {
    this.Invoke( new MethodInvoker( DisplayText ) );
  }
  else {
    DisplayText();
  }
}

private void DisplayText() {
  textBox1.AppendText( "Es hat sich was getan" );
  textBox1.AppendText( Environment.NewLine );
}

Eine andere Möglichkeit, diese threadübergreifenden Vorgänge zu ermöglichen ohne die Eigenschaft InvokeRequired zu prüfen und der Definition/Implementierung von Delegates, die für den Invoke benötigt werden, besteht darin eine Wrapper für die Klasse 'EventService' zu implementieren, der ebenfalls die Schnittstelle 'IEventService' implementiert.

Der Wrapper erhält eine Referenz auf den echten 'EventService' und registriert beim diesem alle angebotenen Ereignisse. Intern erstellt der Wrapper im Konstruktor eine AsyncOperation über den AsyncOperationManager, die er zum Auslösen der eigenen Ereignisse benötigt. Löst nun der echte 'EventService' ein Ereignis aus, wird die EventHandler-Methode des Wrappers aufgerufen. In dieser löst der Wrapper über den Aufruf Post(System.Threading.SendOrPostCallback, object) das entsprechende eigene Ereignis aus.

Dabei wird das Ereignisse des Wrappers in dem Thread ausgelöst, in dem er erstellt wurde. Wenn also der Wrapper im UI-Thread erzeugt wird. z. B innerhalb einer Form, dann werden die Ereignisse des Wrappers im UI-Thread ausgeführt und es kann ein direkter Zugriff auf die Controls erfolgen.

Nachfolgend ist die Implementierung der Wrapper-Klasse für den oben angegebenen 'EventService' dargestellt.


public class WrappedEventService : IEventService {

  private IEventService _realEventService;
  private AsyncOperation _operation;

  public WrappedEventService() {
    _operation = AsyncOperationManager.CreateOperation( Guid.NewGuid() );
  }

  public WrappedEventService( IEventService realEventService ) : this() {
	RealEventService = realEventService;
  }

  public IEventService RealEventService {
    get { return _realEventService; }
    set {
      if ( _realEventService != null ) {
        _realEventService.Changed -= OnChanged;
      }
      _realEventService = value;
      if ( _realEventService != null ) {
        _realEventService.Changed += OnChanged;
      }
    }
  }

  private void OnChanged( object sender, EventArgs e ) {
    _operation.Post( ThrowChangedEvent, e );
  }

  protected virtual void ThrowChangedEvent( object e ) {
    EventHandler changed = Changed;
    if ( changed != null ) {
      changed( this, ( EventArgs ) e );
    }
  }

  #region IEventService Members

  public event EventHandler Changed;

  #endregion
}

In der angehängten Zip habe ich ein Beispielprogramm (Visual Studio 2008 Solution) hinterlegt, um den Sachverhalt praktisch zu verdeutlichen. Der wesentliche Code ist im Konstruktor der Klasse Form1 enthalten. Die wesentlichen Stellen sind mit Kommentaren versehen.

Events, InvokeRequired, AsyncOperation, UI-Thread, Threadübergreifend
Attachments
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo parsifal,

mit scheint es vor dem Hintergrund, dass es es nur einen GUI-Thread geben sollte, etwas viel Aufwand dafür zu sein, weil es ja reichen würde, die AsnycOperation im GUI-Thread zu erzeugen und an den EventService zu übergeben (z.B. über eine entsprechende Property), so dass schon dieser EventService AsnycOperation.Post verwenden kann, ohne dass man einen Wrapper braucht.

Davon abgesehen, entspricht die Namensgebung bei den Events nicht dem Empfehlungen von Microsoft, nach dem EventService.ThrowChangedEvent EventService.OnChanged heißen müsste. Für ChangeSomething gibt es zwar wohl keine offiziellen Vorgaben, aber ich würde es wohl eher FireChanged nennen.

WrappedEventService.OnChanged müsste WrappedEventService-EventService_Changed heißen und WrappedEventService.ThrowChangedEvent WrappedEventService.OnChanged.

herbivore
private Nachricht | Beiträge des Benutzers
Lexodus
myCSharp.de - Member



Dabei seit:
Beiträge: 257
Herkunft: Schweiz

beantworten | zitieren | melden

"In general, you should provide a protected method called OnXxx on types with events that can be overridden in a derived class. This method should only have the event parameter e, because the sender is always the instance of the type."

http://msdn.microsoft.com/en-us/library/h0eyck3s(VS.71).aspx

Grundsätzlich heisst ein Event das gegen aussen verwendet werden kann immer so wie das was es logisch ist.

Also "List.ItemRemoved"

Intern ist es richtig beim remove eine "FireRemovedEvent" Methode zu nennen, auch wenn im Public API nicht interessiert (eigentlich kann der das intern nennen wie er will).

Das ItemRemoved, würde dann eine protected "OnRemove" Methode aufrufen die logischerweise in der ableitenden Klasse überschrieben werden könnte.

Das einzige ungewöhnliche finde ich ist das "FireChangedEvent" Public ist, aber das würde sich dann auflösen wenn mans ein wenig umbaut.

"Für ChangeSomething gibt es zwar wohl keine offiziellen Vorgaben, aber ich würde es wohl eher FireChanged nennen"

Find ich jetzt aber ganz schlecht, da das eigentlich nicht das gleiche ist. Die Methode macht etwas, was ändert und so ein Event schmeisst. Diese Methode in "ThrowEvent" umzubennenne finde ich dann aber irgendwie ganz komisch?

Gruss
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Lexodus am .
If you can't make it, fake it.
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Hi!

Grade habich meine [ExtensionMethods]CrossThread-Calls runderneuert. Damit könntest du ohne weitere Umstände aus einem Nebenthread heraus ein Event im Gui-Thread raisen:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {
   public class EventService2 {

      public event EventHandler Changed;

      /// <summary>
      /// Führt Änderungen an Daten durch und feuert anschließend das Ereignis <see cref="Changed"/>.
      /// </summary>
      public void ChangeSomething() {
         CrossThread.RunGui(ThrowChangedEvent, EventArgs.Empty);
      }

      /// <summary>
      /// Wirft das Ereignis <see cref="Changed"/>.
      /// </summary>
      protected virtual void ThrowChangedEvent(EventArgs e) {
         EventHandler changed = Changed;
         if (changed != null) {
            changed(this, e);
         }
      }
   }
}
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers