www.mikrocontroller.net

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


Autor: Alex (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: Severino R. (severino)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Chris ... (dechavue)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

So wie ich das sehe wendest du Invoke einfach falsch an.
Ich hab das Ganze mal korrigiert.
 
        private void SetText(string text) { 
            //Nicht in GUI-Thread -> Invoke
            if(textBox1.InvokeRequired) textBox1.Invoke(new Action<string>(this.SetText), new object[] {text}); //"Rekursiever" Aufruf

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

        private string txt = "";

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

            //textBoxUpdateDelegate = new TextBoxUpdateDelegate(myPort.ReadExisting);
            //textBox1.Text = this.Invoke(textBoxUpdateDelegate());
        }

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

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich werd's gleich mal lesen und testen.
Vielen Dank.

Gruß, Alex

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du hast einen Debugger.
Du kannst Breakpoints setzen.
Du kannst dein Programm in Einzelschritten durchgehen.

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Arc Net (arc)
Datum:

Bewertung
0 lesenswert
nicht 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
    // Variante 1 
    // Umwandlung anonyme Funktion -> delegate
    delegate void SetTextDelegate(Control c, string s); 
    SetTextDelegate setTextDel = (a, b) => a.Text = b;
    private void SetText(Control c, string s) {
        if (c.InvokeRequired) {
            c.Invoke(setTextDel, new object[] { c, s });
        } else {
            c.Text = s;
        }
    }

    // Variante 2
    private void SetText2(Control c, string s) {
        if (c.InvokeRequired) {
            Action<Control, string> act = new Action<Control, string>((a, b) => addText(a, b));
            c.Invoke(act, new object[] { c, s });
        } else {
            c.Text = s;
        }
    }

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
vielen Dank,

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

Gruß, Alex

Autor: Arc Net (arc)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Alex (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank

Autor: Slava (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Besser - ohne die Methode immer aufzuruffen, Ereigniss ins Queue packen:
private void AddText(String msg)
{
 if (this.textBox1.InvokeRequired)
 {
  this.textBox1.Invoke(new EventHandler(delegate { this.textBox1.Text += msg; }));
 }
 else
 {
  this.textBox1.Text += msg;
 }
}

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.