Laden...

[gelöst] The Thread Startet aber macht nichts | thread 0x129c has exited with code 0 (0x0)

Erstellt von Gurrnder vor 16 Jahren Letzter Beitrag vor 16 Jahren 1.828 Views
G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren
[gelöst] The Thread Startet aber macht nichts | thread 0x129c has exited with code 0 (0x0)

Hallo

Ich wollte meine DB Abfrage auf einen Click Event legen und Zusätzlich noch in einem Thread laufen lassen damit das Fenster zur zeit der Abfrage nicht blockiert wird.

private void button1_Click(object sender, EventArgs e)
        {
            ThreadStart del = new ThreadStart(machmal);
            Thread thr = new Thread(del);
            thr.Start();

        }
        public void machmal()
        {
            
            try
            {
                //Verbindung definieren
                SqlConnection con = new SqlConnection("Server=10.45.51.16;" + //IP des DB Servers
                                                        "Database=EventoFHTest;" + //DB-Name
                                                        "Integrated Security=false;" + //Sicherheitsstufe (false für eigene Benutzerangaben)
                                                        "Password=xxxxxxxxxxxxxx;" + //Login Pwd
                                                        "User ID=xxxxxxxxxxxxxx;"); //Account name;
                //con.Open(); //Verbindung öffnen

                /*String strSQL = "select PersonNachname, PersonVorname " +
                    "from Person where PersonNachname like '%stern%'";*/

                String strSQL = "select IDPerson, PersonNachname, PersonVorname from Person";

                /*while(tbIDPerson.Text != "")
                {
                    //strSQL += " where IDPerson =  || tbPersonNachname.Text != "" || tbPersonVorname.Text != "" 
                }*/
                while (tbPersonNachname.Text != "")
                {
                    strSQL += " where PersonNachname like '" + tbPersonNachname.Text + "%'";
                }
                /*while(tbPersonVorname.Text != "")
                {
                    if (tbPersonNachname.Text = "")
                    {
                        strSQL += " where PersonVorname like '" + tbPersonVorname.Text + "'";
                    }
                    else
                    {
                        strSQL += " and PersonVorname like '" + tbPersonVorname.Text + "'";
                    }
                }                */

                SqlCommand cmd = new SqlCommand(strSQL, con); //Das SqlCommand Objekt sendet den Befehls-String zur Datenbank. Durch con kennt es den DB-Server und die DB.

                DataSet ds = new DataSet(); //Eine art Liste in der die Tabellen-Inhalte abgelebt werden (Array). Ein DataSet kann über mehrere Tabellen Verfügen. Eine Tabelle ist die Rückgabe des SELECT Statement.
                SqlDataAdapter da = new SqlDataAdapter(cmd); //Der SqlDataAdapter bildet eine Verbindung zwischen einem DataSet und SQL Server, über die Daten abgerufen und gespeichert werden.

                /*Ist vor dem aufruf von "da.Fill(ds);" bereits eine Verbindung geöffnet ("com.Open();"),
                 *bleibt die Verbindung bis zum aufruf von "con.Close();" offen.*/
                da.Fill(ds, "stern"); /* Öffnet die Verbindung, Füllt die Daten in den DataSet und 
                                       * Schliesst die Verbindung anschliessend wieder.
                                       * "stern" ist der name der Tabelle und kann auch weggelassen werden*/
                DataTable dt = ds.Tables[0];
                dataGridView2.DataSource = dt;

            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex);
            }
        }

Beim Klicken auf meinem Butten passiert Optisch rein gar nichts. Jedoch bekomme ich im Output fenster folgende Ausgabe:

'GUI-DB Test II.vshost.exe' (Managed): Loaded 'C:\WINDOWS\assembly\GAC_MSIL\System.Configuration\2.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'GUI-DB Test II.vshost.exe' (Managed): Loaded 'C:\WINDOWS\assembly\GAC_32\System.Transactions\2.0.0.0__b77a5c561934e089\System.Transactions.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'GUI-DB Test II.vshost.exe' (Managed): Loaded 'C:\WINDOWS\assembly\GAC_32\System.EnterpriseServices\2.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
System.InvalidOperationException: Cross-thread operation not valid: Control 'dataGridView2' accessed from a thread other than the thread it was created on.
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.Control.SendMessage(Int32 msg, Int32 wparam, Int32 lparam)
   at System.Windows.Forms.Control.BeginUpdateInternal()
   at System.Windows.Forms.DataGridView.RefreshColumns()
   at System.Windows.Forms.DataGridView.OnBindingContextChanged(EventArgs e)
   at System.Windows.Forms.Control.OnParentBindingContextChanged(EventArgs e)
   at System.Windows.Forms.Control.OnBindingContextChanged(EventArgs e)
   at System.Windows.Forms.Control.set_BindingContextInternal(BindingContext value)
   at System.Windows.Forms.ContainerControl.set_BindingContext(BindingContext value)
   at System.Windows.Forms.ContainerControl.get_BindingContext()
   at System.Windows.Forms.Control.get_BindingContextInternal()
   at System.Windows.Forms.Control.get_BindingContext()
   at System.Windows.Forms.DataGridView.DataGridViewDataConnection.SetDataConnection(Object dataSource, String dataMember)
   at System.Windows.Forms.DataGridView.set_DataSource(Object value)
   at GUI_DB_Test_II.Form1.machmal() in C:\Dokumente und Einstellungen\adrian.stern\Eigene Dateien\Visual Studio 2005\Projects\ConsoleApplication1\GUI-DB Test II\Form1.cs:line 86
A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
The thread 0xe30 has exited with code 0 (0x0).


Der Code selber sollte in Ordnung sein da er ja im Konstruktor selber Funktioniert hat.

Aus der Ausgabe selber werde ich nicht schlauf ... evt ist das Problem ja beteits jemandem bekannt.

mfg
Gurrnder

830 Beiträge seit 2005
vor 16 Jahren

Hallo,

FAQ

Gruss
Friedel

Ohne Ziel ist auch der Weg egal.

49.485 Beiträge seit 2005
vor 16 Jahren
G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Danke ...

Ne ich Kapiers immer noch nicht ... Ich kriegs nicht hin das auf mein Problem um zu biegen.


        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(new ThreadStart());
            t.Start();
        }
        
        public void run()  
        {
            
        }

Der Click Event soll den Thread Starten der widerum mein DataGridView verändern soll.
Und in der Methode run() soll folgernder code ausgeführt werden:


            try
            {
                SqlConnection con = new SqlConnection("Server=10.45.51.16;" + 
                                                        "Database=EventoFHTest;" + 
                                                        "Integrated Security=false;" + 
                                                        "Password=xxxxxxxxxxx;" + 
                                                        "User ID=xxxxxxxxxxx;"); 

                String strSQL = "select IDPerson, PersonNachname, PersonVorname from Person where PersonNachname like '%Stern%'";

                SqlCommand cmd = new SqlCommand(strSQL, con); 
                DataSet ds = new DataSet(); 
                SqlDataAdapter da = new SqlDataAdapter(cmd);

                da.Fill(ds); 

                DataTable dt = ds.Tables[0];
                dataGridView2.DataSource = dt; // <-- Knackpunkt?

            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex);
            }

Leider wurde in keinem der gefundenen Beiträge genau Beschrieben welche teile nun für Control.Invoke nötig sind und wo ich diese Anbringen muss. Zumindest konnte ich diese Informationen nicht aus dem Code Lesen.

Wäre cool wenn mir da jemand helfen könnte.

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Die Lösung auch die ich komme wenn ich mich (so glaube ich) an die Vorlagen halte, schaut so aus:

Zu sagen ist, dass die Abfrage und das Auffüllen funktioniert das GUI aber dennoch blockiert wird und das obwohl ich this.InvokeRequired überprüfe --> er macht Invoke!

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(new ThreadStart(run));
            t.Start();
            //run();
        }

        public void run()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(machmal));
            }
            machmal(); // Kann ich auch weg lassen.
        }
        
        public void machmal()  
        {
            try
            {
                SqlConnection con = new SqlConnection("Server=10.45.51.16;" + 
                                                        "Database=EventoFHTest;" + 
                                                        "Integrated Security=false;" + 
                                                        "Password=xxxxxxxxxxx;" + 
                                                        "User ID=xxxxxxxxxxx;"); 

                String strSQL = "select IDPerson, PersonNachname, PersonVorname from Person";

                SqlCommand cmd = new SqlCommand(strSQL, con); 
                DataSet ds = new DataSet(); 
                SqlDataAdapter da = new SqlDataAdapter(cmd);

                da.Fill(ds, "stern"); 

                DataTable dt = ds.Tables[0];
                dataGridView2.DataSource = dt;

            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex);
            }
        }

Bei dem Beispiel steht da immer ctrl.Invoke, was bei mir genauso wenig wie control.Invoke funktioniert ... was soll das bedeuten?

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Gurrnder,

gerade das Invoke blockiert hier das GUI. Alles was du per Invoke ausführst, läuft im GUI-Thread. Aber langlaufende Aktionen müssen gerade in extra Threads laufen. Nur die Zugriffe auf GUI-Elemente erfordern Invoke. Steht aber alles in der FAQ.

herbivore

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Ich blick echt nicht dahinter ... wie soll ich das denn sonst machen.

Die einzige Möglichkeit die ich noch sehe ist eine Methode

        public void run(DataTable dt)
        {
            dataGridView2.DataSource = dt;
        }

zu erstellen und diese per Invoke auf zu rufen oder ich rufe die Methode so

dataGridView2.DataSource = machmal();

direckt aus dem GUI-Thread auf.
In beiden fälle muss die Methode(machmal()) ein DataTable zurück geben was aber irgendwie nicht funktioniert.

363 Beiträge seit 2007
vor 16 Jahren

Du hast momentan wohl einfach einen Denkfehler. Mit Invoke rufst du nur die Methode auf die auf die GUI zugreifen soll. Also geh am besten von deinem ursprünglichen Code aus, der war ja soweit in Ordnung. Nur bei


dataGridView2.DataSource = dt; // <-- Knackpunkt?

ist das Invoke nötig, da du hier von deinem Thread auf ein GUI-Element zugreifst und das ist nicht erlaubt.
Also muss nur für diese Zuweisung Invoke gemacht werden, alles andere ist blödsinn, da du deinen Threadaufruf sonst ad absurdum führst. Es geht ja gerade darum durch den Threadaufruf die langwierige Operation aus dem GUI-Thread auszulagern. Mit Invoke kehrst du ja wieder zu deinem GUI-Thread zurück.
Wenn Invoke ne direkte Methode braucht, dann schreibst halt eine die nur diese eine Zuweisung enthält und fertig.

Gruß Cookiie

"Hail to the King, Baby!"

1.665 Beiträge seit 2006
vor 16 Jahren

Ich hab auch etwas gebraucht um dahinter zu kommen. Merk dir allgemein, wenn du mit Elementen aus einem fremden Thread arbeitest, musst du, wenn du mit den Elementen arbeiten willst, auf die jeweiligen Threads der Elemente Invoken.

Beispiel:

private void TuWas()
{
    Thread thread = new Thread(new ThreadStart(TuWasThread));
    thread.Start();
}

private void TuWasThread()
{
    // Hole DataSource aus der DB
    // DataSource anpassen, etc....

    // DataSource setzen; auf dem Thread des DataGridViews
    SetDataSource(ds.Tables[0]);
}

private void SetDataSource(object dataSource)
{
    if (this.dataGridView.InvokeRequired)
    {
         // Auf Thread von DataGridView invoken
         SetDataSourceCallback del = new SetDataSourceCallback(SetDataSource);
         this.dataGridView.Invoke(del, dataSource);

         return;
    }

    // Nun ists invoked :)
    this.dataGridView.DataSource = dataSource;
}


private delegate void SetDataSourceCallback(object dataSource);
49.485 Beiträge seit 2005
vor 16 Jahren

Hallo JunkyXL,

so machst du aber genau den gleichen Fehler wie Gurrnder, nämlich einen Thread zu starten, der gleich wieder Invoke macht. Das führt wie Cookiie gesagt hat, das Starten des Threads ad absurdum. Es entscheiden fehlt nämlich, dass der extra Thread die eigentlich langlaufende Aktion ausführt und zwar vor dem SetDataSource.

Hallo Gurrnder,

so schwer ist es nun wirklich nicht. Du musst nur systematisch denken.

herbivore

1.665 Beiträge seit 2006
vor 16 Jahren

Original von herbivore
Hallo JunkyXL,

so machst du aber genau den gleichen Fehler wie Gurrnder, nämlich einen Thread zu starten, der gleich wieder Invoke macht. Das führt wie Cookiie gesagt hat, das Starten des Threads ad absurdum. Es entscheiden fehlt nämlich, dass der extra Thread die eigentlich langlaufende Aktion ausführt und zwar vor dem SetDataSource.

Original von Cookiie
Wenn Invoke ne direkte Methode braucht, dann schreibst halt eine die nur diese eine Zuweisung enthält und fertig.

Genau das habe ich doch gemacht, ich gehe ja davon aus, dass im Thread um den Methodenaufruf herum noch einiges passiert und eben die DataSource Zuweisung per Invoke stattfindet. Wenn im Thread nichts weiter passiert als die Zuweisung der DataSource, ist dieser unnötig wie ein Kropp.

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Ich habe das Beispiel von JunkyXL nun bei mir eingebaut, aber das GUI Blockiert immernoch für einige Sekunden.
Was ich nicht verstehe, weil das abfragen der Daten ja in einem Thread läuft.

Wenn ich mit Invoke mein DataGrid befülle, dann ist das ja kein unabhängiger Thread der das macht sondern es ist GUI-Thread der quasi sagt

DataGridView2.DataSourse = dt;

und der läuft erst wider weiter wenn der Befehl abgearbeitet ist. Verstehe ich das richtig?
Kann es also sein, dass das Anzeigen der 50'000 Datensätze das GUI Blockiert und man dagegen auch nichts tun kann?

Ich verstehe auch den Kommentar von herbievore nicht ganz ... also von wegen ad absurdum. Denn für mich schaut der Ablauf ganz sauber aus:

private void TuWas()
{
/* Thread Starten */
    Thread thread = new Thread(new ThreadStart(TuWasThread));
    thread.Start();
}

private void TuWasThread()
{
/* Hier wird zur DB verbunden, die Daten abgefragt und die DataTable erstellt
ausserdem wird die DataTable gleich weiter gegeben zum invoken */
    SetDataSource(deineDataSource);
}

private void SetDataSource(object dataSource)
{
/* Invoken und GUI Aktualisieren */
    if (this.dataGridView.InvokeRequired)
    {
         // Auf Thread von DataGridView invoken
         SetDataSourceCallback del = new SetDataSourceCallback(SetDataSource);
         this.dataGridView.Invoke(del, dataSource);

         return;
    }

    // Nun ists invoked :)
    this.dataGridView.DataSource = dataSource;
}


private delegate void SetDataSourceCallback(object dataSource);

Ich seh da nichts was "unschön" wäre.
Evt. Kann jemand noch ein wort über delegate und SetDataSourveCallback verlieren?

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Gurrnder,

Kann es also sein, dass das Anzeigen der 50'000 Datensätze das GUI Blockiert und man dagegen auch nichts tun kann?

natürlich ist das möglich.

Ich verstehe auch den Kommentar von herbievore nicht ganz ... also von wegen ad absurdum.

Wenn du aus dem GUI-Thread einen Thread startest und dieser Thread quasi sofort die eigentliche Methode per Invoke aufruft, dann läuft die eigentliche Methode im GUI-Thread und nichts ist gewonnen. Du hättest sie auch direkt im GUI-Thread aufrufen können. In beiden Fällen ist der Effekt, dass das GUI blockiert. Du musst einen Thread starten. Dieser muss ohne Invoke die langlaufende Aktion durchführen und erst danach Invoke aufrufen, um die Ergebnisse in das GUI zu füllen. So schwer ist das doch wirklich nicht.

herbivore

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Genau so wie du das Beschreibst, habe ich das oben Kommentiert und auch bei mir gemacht.

GUI-Thread --> DatenSammelThread --> DatenAktualisierInvokeThread(methode wasweisich)

Die 50k Datensätze die ja an die Invoke Methode übergeben werden (DatenSammelThread --> DatenAktualisierInvokeThread(methode wasweisich)) werden dann ja durch Invoke im GUI-Thread ausgeführt ...

Wenn ich das Richtig verstehe kann ich also Nichts gegen das Blockieren meines GUIs tun da ich das Aktualisieren nicht in einem Thread laufen lassen kann. Richtig?

363 Beiträge seit 2007
vor 16 Jahren

Hallo Gurrnder,

bei 50.000 Datensätzen die geschrieben werden müssen, kann ich mir durchausvorstellen das da deine GUI immernoch hängt.
Meine Idee wäre dann, erzeuge in deinem Thread einen neuen dataGridView und belege diesen mit deinen Datensätzen.
Danach caste (mit invoke^^) das neue dataGridView auf das alte in der GUI.

Gruß Cookiie

"Hail to the King, Baby!"

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Hehe ... Wird ja immer besser hier 🙂

Erst mal danke für die Hilfe.

Funktionieren tut das ganze also.
Was bleibt ist noch eine kleine aber wohl nicht unwichtige verständnis Frage.

/* Was ist das?*/
private delegate void SetDataSourceCallback(object dataSource);

Wie kommt ein Laie wie ich darauf das zu schreiben? Wozu dient es genau?
Muss ich für alles was ich am GUI ausgeben will so ein "ding" machen? also sagen wir mal ich will noch die SQL-Queri in String form mit senden wie müsste das denn aussehen?

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Gurrnder,

wenn wirklich das Binden alleine zu lange dauert, dann musst du das in mehrere Schritte aufteilen. Mach - nach dem du alle Daten geladen hast - eine neue DataTable, die - sagen wir - nur die ersten 5000 Elemente enthält und bindest du diese innerhalb des GUI-Threads an das DataGridView. Dann machst du in dem extra Thread eine Schleife und innerhalb der Schleife ein Invoke, in dem du jeweils 5000 weitere Datensätze der gebundenen DataTable hinzufügst. So in der Art sollte es gehen.

herbivore

1.665 Beiträge seit 2006
vor 16 Jahren

Original von Gurrnder

/* Was ist das?*/  
private delegate void SetDataSourceCallback(object dataSource);  

Wie kommt ein Laie wie ich darauf das zu schreiben? Wozu dient es genau?
Muss ich für alles was ich am GUI ausgeben will so ein "ding" machen?

Befrag Google.

also sagen wir mal ich will noch die SQL-Queri in String form mit senden wie müsste das denn aussehen? Dann änderst du die Parameter vom Delegaten entsprechend ab: aus private delegate void SetDataSourceCallback(object dataSource) wird -> private delegate void SetDataSourceCallback(object dataSource, string text)
und musst den zweiten Parameter beim Invoken dementsprechend mit angeben. Desweiteren muss die Methodensignatur genau so aussehen

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

AHAA ...

Also nennt sich das in jedem Fall SetDataSourceCallback. Ich dachte das wäre irgend ein spezieller werte für die Source meiner DataGridView.

Na dann is ja alles klar.
Danke euch nochmals!

mfg
Gurrnder

1.665 Beiträge seit 2006
vor 16 Jahren

Nein, es nennt sich x. Den Namen kannst du selber wählen. Stichwörter bei der google suche: Delegate C#

G
Gurrnder Themenstarter:in
142 Beiträge seit 2007
vor 16 Jahren

Entschuldige meine Dummheit ... bin manchmal nicht ganz so schnell.

So bald etwas wichtig klingt und dann noch Hervorgehoben wird habe ich immer das gefühl es ist system seitig.

danköö