Entwickler-Ecke

Basistechnologien - Elegantes Callback aus einem Thread zur GUI


jfheins - So 06.01.13 14:24
Titel: Elegantes Callback aus einem Thread zur GUI
Hi,
ich bin immer noch bei meinem tollen Programm und hätte da mal eine Frage zu "Eleganz" :)

Folgende Lösung funktioniert zwar, sieht aber nicht besonders hübsch aus:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
public partial class MainForm : Form
{
  // Diese Methode wird duch den COM Port stats in einem seperaten Thread aufgerufen
  private void COM_Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  {
    var port = (SerialPort)sender;
    while (port.BytesToRead > 0)
    {
      Transmitter.ByteReceived((byte)port.ReadByte(), DisplayPacket);
    }
  }
  
  // Hier habe ich Zugriff auf this und kann daher BeginInvoke benutzen
  private void DisplayPacket(COMM_Header header, byte[] payload)
  {
    if (this.InvokeRequired)
      this.BeginInvoke(new DisplayDelegate(DisplayPacket), header);
    else
      info_textBox.AppendText("Paket empfangen: " + header.Type.GetInfo().DisplayName + "\n");
  }
}

public class Comm_Transmitter
{
  // Demzufolge läuft auch diese Methode nicht im GUI Thread
  public void ByteReceived(byte value, DisplayDelegate callback)
  {
    // viel code
    // Hier habe ich keine referenz auf das Formular, daher kein BeginInvoke
    callback(RecHeader, null);
  }
}

Was mir jetzt einfällt: Das InvokeRequired kann vielleicht 'raus weil ich weiß dass es nötig ist. Das Formular weitergeben nur um damit dann BeginInvoke aufzurufen klingt noch uneleganter...?
Jetzt hatte ich mir überlegt, sowas wie "this.BeginInvoke(new DisplayDelegate(DisplayPacket), ...)" als callback zu übergeben. Sieht aber wieder umständlich aus mit zwei verketteten delegaten...

Wie macht ihr sowas?


Christian S. - So 06.01.13 14:57

Hallo!

Ich schreibe mir immer ganz gerne generische Methoden, die mir den Aufruf im GUI-Thread vereinfachen. Zum Beispiel so:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
  public void Invoke<T>(Action<T> action, T param)
  {
    if (this.InvokeRequired)
      Invoke(action, param);
    else
      action(param);            
  }



  private void DisplayPacket(COMM_Header header, byte[] payload)
  {
    Invoke((aHeader) => info_textBox.AppendText("Paket empfangen: " + header.Type.GetInfo().DisplayName + "\n"), header);
  }


Generell finde ich, dass anonyme Methoden und Lambda-Ausdrücke bei Multithreading helfen, Code dort stehen zu haben, wo er von der Logik her stehen sollte :-)


Könntest Du nicht die gesamte ByteReceived-Methode bereits im GUI-Thread aufrufen oder dauert die zu lange und sollte parallel abgearbeitet werden?

Oder kann die ByteReceived-Methode nicht einfach den entsprechenden COMM_Header als Ergebnis zurück gegeben?

Viele Grüße,
Christian


jfheins - So 06.01.13 16:14

Hi,
Zitat:
Könntest Du nicht die gesamte ByteReceived-Methode bereits im GUI-Thread aufrufen oder dauert die zu lange und sollte parallel abgearbeitet werden?

So habe ich das jetzt gelöst :-)

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
        private void COM_Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            var port = (SerialPort)sender;
            while (port.BytesToRead > 0)
            {
                var data = (byte)port.ReadByte();
                this.BeginInvoke((Action)(() => Transmitter.ByteReceived(data, DisplayPacket)));
            }
        }

Dann wird zwar für jedes empfangene Byte der Hauptthread belästigt, aber das sollte bei unserer Datenrate noch machbar sein :P