Laden...

Ungültiger Threadübergreifender Vorgang

Erstellt von sbertl000 vor 18 Jahren Letzter Beitrag vor 16 Jahren 39.442 Views
sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren
Ungültiger Threadübergreifender Vorgang

Also ich habe eine Methode die mit einem Thread aufgerufen wird, sie soll dann einen text in eine textBox schreiben ich erhalte aber immer eine invalidoperationexception, üngültiger threadübergreifender vorgang. Wie greife ich denn dann Threadübergreifend zu:

private void thread1()
        {
            Thread t = new Thread(new ThreadStart(test));
            t.Start();
        }

        private void test()
        {
            TextBox1.Text = "testtest";
        }
S
8.746 Beiträge seit 2005
vor 18 Jahren
347 Beiträge seit 2006
vor 18 Jahren

Du darfst nicht von einem anderen Thread auf GUI Controls zugreifen.
Die GDI+ ist nicht thread safe, deshalb prüft SWF ziemlich genau ob ein call vom richtigen Thread kam. 😉

Ab Control ist die Methode Invoke verfügbar. Der kannst du eine Methode und (optional) Parameter übergeben. Diese Methode wird dann im GUI-Thread ausgeführt und alles ist in Butter. 🙂

Bleistift:

private void thread1()
{
  ThreadStart asyncCall = delegate
  {
      TextBox1.Invoke(test);
  };

  Thread t = new Thread(asyncCall);
  t.Start();
}

private void test()
{
   TextBox1.Text = "testtest";
}

oder du schaust dir einfach mal das hier an. 😉

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

oh mann das sieht ja (für mich) kompliziert aus. könntest du mir vieleicht einen tipp zu meinem beispiel geben?

//Edit: nehme alles zurück hab den anderen beitrag nicht gesehen, werd ihn mir anschauen

S
8.746 Beiträge seit 2005
vor 18 Jahren

Schätze mal, dass du Roberts Posting übersehen hast. Noch einfacher als dieser (2.0) Code gehts wohl kaum. Unter 1.1 ist es ein wenig geschwätziger. Trotzdem würde ich empfehlen, dich hier mit der Materie vertraut zu machen. Threading ist nicht ganz trivial, aber notwendig wenn man ernsthaft WinForms-Programmierung betreibt.

// Edit, ich höre jetzt auf, diese Überschneidungen sind ja lästig... 😉

347 Beiträge seit 2006
vor 18 Jahren

Original von sbertl000
oh mann das sieht ja (für mich) kompliziert aus. könntest du mir vieleicht einen tipp zu meinem beispiel geben? Habe ich eigentlich...
btw: Antworte bitte direkt auf den Beitrag, auf den du dich beziehst. Sonst sieht man nicht wen du überhaupt meinst.... 🤔

*Falls* du mich meintest: Wo hast du denn genau Probleme?

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

ok ich habe deinen Code probiert, der funktioniert aber nicht. Oder mache ich etwas falsch (wert ist in diesem Fall die textBox)

private void thread1()
        {
            ThreadStart asyncCall = delegate
            {
                wert.Invoke(test); //Argument 1 kann nicht von Methodengruppe in System.Delegat konvertiert werden
            };

            Thread t = new Thread(asyncCall);
            t.Start();
        }

        private void test()
        {
            wert.Text = "testtest";
        }
347 Beiträge seit 2006
vor 18 Jahren

Original von sbertl000
ok ich habe deinen Code probiert, der funktioniert aber nicht. Oder mache ich etwas falsch (wert ist in diesem Fall die textBox) D'oh! 8o
Habe nicht gesehen, dass du noch VS03 und somit .Net 1.1 benutzt....
C#1.1 kann noch nicht autom. den richtigen Delegaten generieren (hier Threadstart) und anonyme Methoden gab es damals auch noch nicht.

So könnte die "klassische" Lösung aussehen:1.eine Methode, die im Thread rennt (AsyncCall) 1.eine Methode, die an den GUI Thread gehängt werden kann (test)

private void thread1()
{
  Thread t = new Thread(new ThreadStart (AsyncCall));
  t.Start();
}

private void AsyncCall()
{
      TextBox1.Invoke(test);
};

private void test()
{
   TextBox1.Text = "testtest";
}
sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

eigentlich benutze ich schon net 2.0.

...wird sofort im profil geändert!!

347 Beiträge seit 2006
vor 18 Jahren

Oki, mein Fehler. Ich hätte testen sollen was ich hier tippe. X(

Control.Invoke mag es anscheinend gar nicht wenn man ihm eine reine Methode vorwirft.
Du musst ihn an der Stelle wieder "zu Fuss" mit einem Delegaten verpacken:

TextBox1.Invoke(new MethodInvoker(test));
sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

danke jetzt hats funktioniert

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

ich hätte allerding noch eine frage: gibt es ein event, dass eintritt, wenn der thread erfolgreich abgearbeitet ist? ich hab keins gefunden.

564 Beiträge seit 2006
vor 18 Jahren

Hallo sbertl000!

Nein, den musst du dir selbst schreiben und diesen am Thread-Ende feuern.
Wie es geht, wurde hier
--> Frage zu einem eigenen Event
--> Problem mit EventHandler
beschrieben.

der Marcel

:] 😄Der größte Fehler eines modernen Computers sitzt meist davor 😁 :]

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

ok, danke

S
84 Beiträge seit 2005
vor 18 Jahren

Es geht auch noch schneller:
Ich bin ein grosser Fan der anonymen Methoden, und hier ist der perfekte Einsatzort für sie!
Anstatt


private void test()
{
   TextBox1.Text = "testtest";
} 

TextBox1.Invoke(new MethodInvoker(test));

einfach so:


TextBox1.Invoke(new MethodInvoker(delegate(){ TextBox1.Text = "testtest"; }));

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

danke, ich hätte allerdings noch eine frage, bei euren Beispielen mit den delegates läuft der thread irgendwie nicht synchron zu anderen, da, wenn der Thread läuft das WinForm "hängen bleibt". Ich hab nämlich ein kleinen prog zum Berechnen von MD5 Checksummen geschreiben, bei dem Programm tritt der oben genannte Effekt ein.

private void MD5Hash()
        {
            byte[] hashArray;
            string ergebnis = "";
            string pfad = ladeDatei.FileName;

            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); //Erstellt eine neue Instanz des MD5CryptoService Provider

            fs = new FileStream(pfad, FileMode.Open); //Lädt die angegebene Datei in einen Stream
            hashArray = md5.ComputeHash(fs); //Berechnet des Hash-Wert des Streams und speichert den Wert in ein byte[]
            fs.Close(); //Schließt den Stream

            ergebnis = BitConverter.ToString(hashArray).Replace("-", ""); //Konvertiert das Hash-Array in einen String

            wert.Text = ergebnis;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = null;

            if (ladeDatei.ShowDialog() == DialogResult.OK)
            {
                ThreadStart asyncCall = delegate
                {
                    wert.Invoke(new MethodInvoker(MD5Hash));
                };

                t = new Thread(asyncCall);
                t.Start();
            }
        }
S
8.746 Beiträge seit 2005
vor 18 Jahren

Ich glaube, dir ist die Funktionsweise von Invoke noch nicht 100% klar:

Dein Thread macht ja nix weiter, als via Invoke die Kontrolle zurück an den UI-Thread zu geben (wert.Invoke(new MethodInvoker(MD5Hash))). Damit läuft dein gesamte Code wieder im UI-Thread und ist durch die ist natürlich durch die MD5-Berechnung wieder blockiert. Du solltest NUR und ausschließlich diesen Code via Invoke aufrufen:

wert.Text = ergebnis;

Am besten sogar statt Invoke() BeginInvoke() verwenden.

Also: M55Hash() als Thread starten und darin Invoke() aufrufen.

Merke: Über Invoke() sollte ausschließlich UI-Code aufgerufen werden, bzw. kurz laufende Operationen.

1.373 Beiträge seit 2004
vor 18 Jahren

Zu dem MethodInvoker: warum nicht einfach so:


TextBox1.Invoke( (EventHandler) delegate { TextBox1.Text = "foo"; } );

Grüße,
Andre

sbertl000 Themenstarter:in
822 Beiträge seit 2005
vor 18 Jahren

danke, jetzt hats funktioniert!

S
84 Beiträge seit 2005
vor 18 Jahren

Original von VizOne
Zu dem MethodInvoker: warum nicht einfach so:

  
TextBox1.Invoke( (EventHandler) delegate { TextBox1.Text = "foo"; } );  
  

Grüße,
Andre

Ist doch fast das gleiche wie das was ich geschrieben habe, nur mit dem unterschied das du das eventhandler delegat dafür missbrauchst! Für sowas gibt es ja gerade den MethodInvoker (das delegate nich tmit der klasse verwechseln!)

D
586 Beiträge seit 2004
vor 17 Jahren

Ich habe das selbe Problem und habe nun versuch meine Form umzustellen:


ThreadStart asyncCall = delegate
{
     myform = new Form();
     myform.Invoke(new MethodInvoker(Form.ShowForm));
};
myform = new Thread(asyncCall);

Das Ganze wird im Load gerufen. Nun folgt ein weiterer Fehler: Invoke oder BeginInvoke kann für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde.
Ich komme einfach nicht so richtig weiter. Vielleicht hat jemand einen guten Rat?!

:::

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo diana,

dann erstellst du den Handle halt: CreateHandle.

herbivore

D
586 Beiträge seit 2004
vor 17 Jahren

Ist jetzt die Frage wie: Wenn ich base.CreateHandle() ins Load vor alles andere schreibe, dann kommt der Hinweis, das dieses Hadle schon vorhanden ist.

:::

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo diana,

du musst natürlich den Handel von dem Control erzeugen, das du für das Invoke verwendet. Da aber normalerweise alle Controls im selben Thread laufen, kann man auch ein anders Control, dessen Handle schon erzeugt ist, für Invoke verwenden.

Wobei - wenn ich mir deinen Code genauer angucke - scheinst du myform in einem extra Thread zu erzeugen. Das ist nicht gut! Erzeuge alle Forms im GUI-Thread.

Davon abgesehen ist eine Konstrunktion wie
myform = new Form();
myform.Invoke ...
eh witzlos, weil unmittelbar nach myform = new Form() sowieso myform.InvokeRequired false ist.

herbivore

D
586 Beiträge seit 2004
vor 17 Jahren

Leider ist der Code nicht von mir und ich steige nicht so richtig durch warum das Fenster in einem anderem Thread läuft. Ich wollte den Code nur anpassen, damit die Anwendung wieder läuft, aber leider funktioniert das so nicht. Warte, ich glaube das mit den Threads wird benötigt um die eine Form zu einem bestimmten Zeitpunkt zu rufen und dann parallel zur eigentlichen Form laufen zu lassen, aber wie gesagt hinter den Code bin ich selbst noch nicht gestiegen. Also kann ich das mit dem new Thread nicht einfach weglassen.

:::

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo diana,

es sieht so aus, als hätte der Ersteller des Codes nicht gewusst, was er tut. Es hilft m.E. nichts anderes als den Code neu zu strukturieren. Alle GUI-Elemente sollte vom GUI-Thread erzeugt werden. Andere Threads sollten keine GUI-Elemente erzeugen.

herbivore

D
586 Beiträge seit 2004
vor 17 Jahren

Was heißt das jetzt für meinen Code, sorry, ich habe noch nicht mit Threads gearbeitet. Wichtig ist dann ja die von mir schon angepasste Codestelle:


ThreadStart asyncCall = delegate
{
    thread = new LfLSplashScreen();
    thread.Invoke(new MethodInvoker(Form.ShowForm));
};
thread = new Thread(asyncCall);
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

Wie mache ich daraus einen GUI-Thread bzw. wie mache ich das anders? So langsam verstehe ich den Code: Es wurde ein neuer Thread verwendet, damit ein kleines Startfenster beim Öffnen der Anwendung vorgeschalten wird, auf dem angezeigt wird (per Fortschrittsbalken) welche Dinge vorher noch geprüft werden. Es muss ja parallel laufen. Wenn alles abgeprüft ist, dann verschwindet die Frm wieder und die Anwendung ist offen.

:::

D
586 Beiträge seit 2004
vor 17 Jahren

Löööösssung:

Ich habe einen Delegaten für mein Anzeigen der Form gebaut, dann AsyncCallback verwendet und dann mit BeginInvoke neuen Thread gestartet. Ich konnte mich noch dunkel daran erinnern sowas schon mal gemacht zu haben. Habe aber nicht daran gedacht, dies auch hier anwenden zu können. Meine Anwendung läuft wieder. Vielen Dank für die Hilfe herbivore🙂

:::

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo diana,

damit ein Fenster trotz einer langlaufenden Aktion nicht blockiert, muss nicht das Fenster, sondern die langlaufende Operation in einen extra Thread. Siehe [FAQ] Warum blockiert mein GUI?

herbivore

D
586 Beiträge seit 2004
vor 17 Jahren

Stimmt, habe auch die Funktion in den neuen Thread gegeben.

:::

D
586 Beiträge seit 2004
vor 17 Jahren

Zu früh gefreut, wenn ich meinen Thread beenden will, also der Aufruf dazu, dann bringt er mir die alte Fehlermeldung: Threadübergreifender Vorgang....
Nun bin ich wieder am Anfang:-(

:::

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo diana,

vergiss den bestehenden Code und mach es wie in [FAQ] Warum blockiert mein GUI? beschrieben.

herbivore

Z
322 Beiträge seit 2006
vor 16 Jahren

Hallo, habe folgenden Code:


    private void testProzedur()
    {
      RTB_Log.Invoke(new MethodInvoker(test("s")));
    }
    private void test(string TmpString)
    {
      RTB_Log.Text = TmpString;
    } 


Aufruf:


    private void button2_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(testProzedur);
         t.Start();
     }

Frage: wie kann ich bei der Zeile:

RTB_Log.Invoke(new MethodInvoker(test("s")));

meinen String übergeben? danke im voraus.

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo ZeroQool,

es geht wie in den Codebeispielen in der FAQ gezeigt.

herbivore

85 Beiträge seit 2006
vor 16 Jahren

Edit: Ich frage lieber in nem Forum wo einem geholfen wird