Laden...

Event aus anderen Threads im GUI-Thread behandeln

Erstellt von Viper2000 vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.547 Views
V
Viper2000 Themenstarter:in
63 Beiträge seit 2008
vor 13 Jahren
Event aus anderen Threads im GUI-Thread behandeln

Hallo,

ich nutze die API eines Displayherstellers um ein USB-LCD Display anzusprechen. Im GUI-Thread (in meiner Form) erzeuge ich ein Objekt der Diplayklasse und registriere einen EventHandler. Der Eventhandler wird aufgerufen wenn die API eine Logmeldung auszugeben hat. Diese Logmeldung schreibe ich dann im EventHandler in eine ListBox auf meiner Form. Das funktioniert auch alles super solange ich die ganze Logik des Displays im GUI-Thread erledige. Da dies aber aus diversen Gründen nicht geht habe ich die ganze Funktionalität in einen separaten Thread ausgelagert.

Das Objekt der Diplayklasse übergebe ich in meiner Form an den Konstruktor einer von BackgroundWorker abgeleiteten Klasse die die ganze Arbeit mit dem Display in einem seperaten Thread erledigt.

Sobald nun der Log-Eventhandler aufgerufen wird (der ja im GUI Thread registriert ist) kommt es zum "ungültigen Threadübergreifenden Vorgang". Das aber nicht immer! Nur in dem Falle wenn im BackgroundWorker ein anderes Event der API ausgelöst wird (ScrollFinished Event - Wenn das Display einen kompletten Text-Scrolldurchgang erledigt hat). Ich erkenne keinen direkten Zusammenhang warum dieses Event etwas direkt mit dem Log zu tun haben sollte (denn alle anderen Logmeldungen die nicht durch das ScrollFinished Event im BackgroundWorker ausgelöst werden kommen ja durch).
Dafür habe ich noch keine geeignete Lösung gefunden. Der Sachverhalt ist nicht ganz einfach, ich hoffe ich konnte es halbwegs verständlich rüberbringen. Hier ein paar Code-Snippets:

GUI Klasse:


private iMonWrapperApi imon; //Das ist die API
private DisplayHandler displayHandler; //Das ist die von BackgroundWorker abgeleitete Klasse die die Logik enthält
this.imon = new iMonWrapperApi();
this.imon.Log += wrapperApi_Log; //Registrierung des Eventhandlers im GUI Thread
this.imon.StateChanged += wrapperApi_StateChanged;  //Wenn z.b. ein Diplay vom USB angeschlossen oder abgezogen wird wird dieses Ereignis aufgerufen
this.displayHandler = new DisplayHandler(this.imon); //Übergabe des API Objekts an den BackgroundWorker
this.displayHandler.RunWorkerAsync(); //Starte den Backgroundworker

private void wrapperApi_Log(object sender, iMonLogEventArgs e)
{            
    listBoxLog.Items.Add(e.message);
}

BackGroundWorker Klasse:


class DisplayHandler : BackgroundWorker
{
      public DisplayHandler(iMonWrapperApi imon)
      {
           .....
           this.imon.StateChanged += stateChanged;
      }
      protected override void OnDoWork(DoWorkEventArgs e)
      {
           //Hier wird die Arbeit verrichtet
      }

      private void stateChanged(object sender, iMonStateChangedEventArgs e)
      {
             //Falls ein LCD Display angeschlossen wurde unterstützt es die Scrollfunktion und der passende EventHandler muss registriert werden. Bei anderen Displaytypen wird dieser Eventhandler wieder getrennt!
            if (diplayType == iMonDisplayType.LCD)
                 this.imon.LCD.ScrollFinished += lcdScrollFinished;
            else
                 this.imon.LCD.ScrollFinished -= lcdScrollFinished;
      }
      private void lcdScrollFinished(object sender, EventArgs e)
       {
            Thread.Sleep(1000);   //Warte eine Sekunde
            
             this.update(); //Beginne Text von Vorne aufs LCD zu schreiben
            
      }
}

Ich hoffe Ihr habt ein paar Tips für mich.
Viele Grüße Viper

Gelöschter Account
vor 13 Jahren

wenn die Exception kommt musst du die Methode umschreiben wie hier beschrieben: [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

V
Viper2000 Themenstarter:in
63 Beiträge seit 2008
vor 13 Jahren

Ich habe mir nun folgendes zusammengefrickelt. Es funktioniert damit tatsächlich. Da ich einen EventHandler Invoke konnte ich nicht den Lösungsweg mit dem "MethodInvoker" nehmen wie im FAQ beschrieben. Ist meine Lösung ok oder gibt es da einfacherer/elegantere Arten sowas zu beheben? Ich hab wirklich nur solange rumprobiert bis es funktioniert hat...wie es genau funktioniert habe ich noch nicht begriffen


private delegate void wrapperApi_Log_Delegate(object sender, iMonLogEventArgs e);
    
private void wrapperApi_Log(object sender, iMonLogEventArgs e)
{
      wrapperApi_Log_Delegate dele = wrapperApi_Log;

      if (this.InvokeRequired)
      {
          object[] args = {sender,e};
          this.BeginInvoke(dele, args);
          return;
      }

      listBoxLog.Items.Add(e.message);  
}

916 Beiträge seit 2008
vor 13 Jahren

Es gibt keine andere Möglichkeit. Kurz zum Hintergrund des Delagaten. Dein Event wird aus einem Thraed gefeuert welcher nicht der Thread ist der das Steuerelement ertsellt hat was du in dem Event ansprechen möchtest. Immer dann wenn du also auf Controls zugreifst (Lesen/Schreibend) und du nicht im GUI Control Erstellthread bist, musst du das ausführen an ein Delegaten weitergeben, der nichts weiter macht als diese Methode im GUI Thread auszuführen. Das kannst du entweder wie in deinem Beispiel über BeginInvoke machen oder aber über Invoken machen.

Again what learned...