Forum: PC-Programmierung C#, Multithread, Invoke, Delegates - Erklärung zur Fehlermeldung


von Alex (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe folgendes Problem:

ich möchte Daten aus dem SerialPort lesen und in einer Textbox 
darstellen (ich weiß, das gab's sicher schon tausend Mal). Nun habe ich 
schon herausgefunden (ich bin noch ziemlich grün hinter den Ohren, was 
C# und Visual Studio angeht), dass das ganze als Multithread läuft (Form 
und Serielle Schnittstelle) und deshalb mittels Delegate und Invoke 
gelöst werden muss. Leider gibt mein superdickes sonst ganz nettes 
C#-Buch nichts über Invoke her. Kurzum, ich bekomme eine Fehlermeldung, 
was die Argumente des Delegates angeht. Und auch bei der Invoke-Methode 
meckert er etwas an.
Ich habe ein Delegate erzeugt, das dann die SerialPort.ReadExisting() 
Methode aufnehmen soll. Mittels Invoke soll dann im DataReceivedEvent 
der Schnittstelle die TextBox aktualisiert werden.
Schaut mal bitte kurz auf den Quellcode und helft mir weiter.


Danke, Alex

von Severino R. (severino)


Lesenswert?

Alex schrieb:

> Leider gibt mein superdickes sonst ganz nettes
> C#-Buch nichts über Invoke her.

Dafür die Suchfunktion hier im Forum:
http://www.mikrocontroller.net/search?query=invoke&forums[]=8&max_age=-&sort_by_date=0

von Alex (Gast)


Lesenswert?

Danke erstmal, vielleicht kann mir trotzdem noch jemand sagen, warum ich 
immer den Fehler bekomme, dass das Delegate keine 1-Aurgumente aufnehmen 
könne. Meine Delegate ist doch vom selben Typ wie die Funktion, die er 
übergeben soll. Woher dann die Fehlermeldung?

Danke, Alex

von Chris .. (dechavue)


Lesenswert?

Hi,

So wie ich das sehe wendest du Invoke einfach falsch an.
Ich hab das Ganze mal korrigiert.
1
 
2
        private void SetText(string text) { 
3
            //Nicht in GUI-Thread -> Invoke
4
            if(textBox1.InvokeRequired) textBox1.Invoke(new Action<string>(this.SetText), new object[] {text}); //"Rekursiever" Aufruf
5
6
            //In GUI Thread -> Text setzten
7
            else textBox1.Text = text;
8
        }
9
10
        private string txt = "";
11
12
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
13
        {
14
            txt += myPort.ReadExisting();
15
            SetText(txt);
16
17
            //textBoxUpdateDelegate = new TextBoxUpdateDelegate(myPort.ReadExisting);
18
            //textBox1.Text = this.Invoke(textBoxUpdateDelegate());
19
        }

Hier noch ein guter Artikel zu dem Thema: 
http://www.mycsharp.de/wbb2/thread.php?threadid=33113

greets

PS: es wäre immer hilfreich wenn du die exakte Fehlermeldung postest

von Alex (Gast)


Lesenswert?

Ich werd's gleich mal lesen und testen.
Vielen Dank.

Gruß, Alex

von Alex (Gast)


Lesenswert?

Hallo Chris E.

nochmal viele Dankj für dein Tip, hat alles funktioniert. Beim 
nachvollziehen gibt es noch eine kleine Unklarheit, vielleicht kannst du 
mir nochmal kurz weiterhelfen.

wenn if(textBox.InvokjeRequered) == true, wird wieder SetText aufgerufen 
usw.

Mir ist etwas unklar, wie und wann dieser "Rekursive Aufruf" endet.

Gruß, Alex

von Karl H. (kbuchegg)


Lesenswert?

Alex schrieb:

> Mir ist etwas unklar, wie und wann dieser "Rekursive Aufruf" endet.

Normalerweise nach dem ersten 'rekursiven Aufruf', der im übrigen nicht 
wirklich eine Rekursion ist.

Im Grunde geht es nur darum, den Kontext auf den anderen Thread 
umzuschalten. InvokeRequired stellt fest ob dies notwendig ist und 
startet die Funktion nochmal in diesem Kontext.

von Alex (Gast)


Lesenswert?

d.h. beim zweiten Durchlauf der Funktion liefert dann

if(textBox.InvokjeRequered) == false und der else-Zweig wird ausgeführt,

verstehe ich das richtig?

Gruß, Alex

von Karl H. (kbuchegg)


Lesenswert?

Du hast einen Debugger.
Du kannst Breakpoints setzen.
Du kannst dein Programm in Einzelschritten durchgehen.

von Alex (Gast)


Lesenswert?

Hi, möchte die von Chris oben genannte Lösung gern so erweitern, dass 
ich universell von jedem Control den Text ändern kann. Ich hab das wie 
folgt versucht (s.u.), komme aber mit der Argumentübergabe des 
Action<T1, T2> Delegates nicht so richtig klar (hab schon allerlei 
Varianten versucht), sodass ich immer eine ArgumentException bekomme.

private void SetText(string text, Control myControl)
{
     //Nicht in GUI-Thread -> Invoke
     if(myControl.InvokeRequired) myControl.Invoke(new Action<string,
     Control>(this.SetText), new object[] {text}, new object());
     //"Rekursiever" Aufruf

     //In GUI Thread -> Text setzten
     else myControl.Text = text;
}

        private string txt = "";

private void serialPort1_DataReceived(object sender, 
SerialDataReceivedEventArgs e)
{
      txt += myPort.ReadExisting();
      SetText(txt, myTextBox);
}


Für den entscheidenden Hinweis wäre ich dankbar,

Gruß, Alex

von Arc N. (arc)


Lesenswert?

Alex schrieb:
> Hi, möchte die von Chris oben genannte Lösung gern so erweitern, dass
> ich universell von jedem Control den Text ändern kann. Ich hab das wie
> folgt versucht (s.u.), komme aber mit der Argumentübergabe des
> Action<T1, T2> Delegates nicht so richtig klar (hab schon allerlei
> Varianten versucht), sodass ich immer eine ArgumentException bekomme.
>
> private void SetText(string text, Control myControl)
> {
>      //Nicht in GUI-Thread -> Invoke
>      if(myControl.InvokeRequired) myControl.Invoke(new Action<string,
>      Control>(this.SetText), new object[] {text}, new object());
>      //"Rekursiever" Aufruf
>
>      //In GUI Thread -> Text setzten
>      else myControl.Text = text;
> }
> Für den entscheidenden Hinweis wäre ich dankbar,
>
> Gruß, Alex

Übersichtlicher wird's, wenn man anonyme Funktionen verwendet
1
    // Variante 1 
2
    // Umwandlung anonyme Funktion -> delegate
3
    delegate void SetTextDelegate(Control c, string s); 
4
    SetTextDelegate setTextDel = (a, b) => a.Text = b;
5
    private void SetText(Control c, string s) {
6
        if (c.InvokeRequired) {
7
            c.Invoke(setTextDel, new object[] { c, s });
8
        } else {
9
            c.Text = s;
10
        }
11
    }
12
13
    // Variante 2
14
    private void SetText2(Control c, string s) {
15
        if (c.InvokeRequired) {
16
            Action<Control, string> act = new Action<Control, string>((a, b) => addText(a, b));
17
            c.Invoke(act, new object[] { c, s });
18
        } else {
19
            c.Text = s;
20
        }
21
    }

von Alex (Gast)


Lesenswert?

vielen Dank,

Variante 1 funktioniert. Bei Variante 2 bekomme ich eine Fehlermeldung 
für addText. Meintest Du AddText?

Gruß, Alex

von Arc N. (arc)


Lesenswert?

Alex schrieb:
> vielen Dank,
>
> Variante 1 funktioniert. Bei Variante 2 bekomme ich eine Fehlermeldung
> für addText. Meintest Du AddText?
>
> Gruß, Alex

SetText2 war gemeint...

von Alex (Gast)


Lesenswert?

Vielen Dank

von Slava (Gast)


Lesenswert?

Besser - ohne die Methode immer aufzuruffen, Ereigniss ins Queue packen:
1
private void AddText(String msg)
2
{
3
 if (this.textBox1.InvokeRequired)
4
 {
5
  this.textBox1.Invoke(new EventHandler(delegate { this.textBox1.Text += msg; }));
6
 }
7
 else
8
 {
9
  this.textBox1.Text += msg;
10
 }
11
}

von Markus (Gast)


Lesenswert?

Hallo zusammen,

Thread ist schon etwas älter, aber imer noch aktuell.
Ich verwende seit Jahren in etwa die Variante 1.
Ab und zu kommt es vor, das ich ein Programm beende und die Ausführung 
genau bei c.Invoke(... hängen bleibt. Das Programm wird dann nie 
beendet.
Hat jemand für dieses Problem eine elegante Lösung?
Momentan versuche ich es auf diese Weise zu lösen:

if (!this.Disposing) c.Invoke(...

Bin aber nicht sicher ob das immer funktioniert, wie gesagt, tritt nur 
sehr selten auf und lässt sich schlecht nachvollziehen.

Gruß, Markus

von Markus (Gast)


Lesenswert?

Lösung gefunden!

Ist zwar etwas unschön, da es eigentlich über Standard Methoden zu 
erledigen sein sollte, hat aber alles nicht geklappt.
Ich setze im Formclosing-Event eine globale boolsche Variable 
isFormClosing auf true, die ich vor dem Invoke-Aufruf abfrage. Schon 
klappts!
Alle anderen Varianten mit Abfrage von this.Disposing oder c.Disposing 
usw. funktionieren nicht. Anscheinend werden diese Objekt-Eigenschaften 
zu spät gesetzt.

Code-Beispiel (funktioniert natürlich auch mit Label etc. statt Textbox:

bool formIsClosing = false;

/// <summary>
/// Is called when the form is closing
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CSMLParser_FormClosing(object sender, FormClosingEventArgs 
e)
{
  formIsClosing = true;
}


/// <summary>
/// A delegate for thread safe text display
/// </summary>
/// <param name="tb">The textbox-object which is to be used</param>
/// <param name="msg">The message that has to be shown</param>
delegate void showTextinTBCB(TextBox tb, String msg);
/// <summary>
/// Shows the text msg in textbox tb
/// </summary>
/// <param name="tb">The textbox-object which is to be used</param>
/// <param name="msg">The message that has to be shown</param>
public void showTextinTB(TextBox tb, String msg)
{
  try
  {
    if (tb.InvokeRequired)
    {
      showTextinTBCB d = new showTextinTBCB(showTextinTB);
      if (!formIsClosing)
        Invoke(d, new object[] { tb, msg });
      //else
      //    MessageBox.Show("Invoke-Error");
    }
    else
    {
      tb.Text = msg;
      tb.ScrollToCaret();
    }
  }
  catch (Exception) { }
}



Gruß, Markus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.