[C#] Form hängt trotz Auslagerung in anderen Thread

real-insanity

Erfahrenes Mitglied
Hallo zusammen,

ich habe nun folgendes Problem:
In meiner Anwendung werden aus einer DB alle CDs mit den entsprechenden Tracks in einem Treeview aufbereitet und ausgegeben (siehe Anhang).
Da die Datenmenge hoch sein (200 CDs á 11 Tracks) kann und ich nicht möchte, dass die Form hängt, rufe ich die Funktion in einem anderen Thread aus.
Dennoch hängt bei mir die Form, sobald der Treeview aufbereitet wird.

Hier der Aufruf der Funktion:
Code:
...
new Thread(new ThreadStart(FillTreeView)).Start();
...

Die Funktion:
Code:
private void FillTreeView()
        {
            TreeNode root;
           
            if (treeView1.InvokeRequired)
            { // Wenn Invoke nötig ist, ...
                // dann rufen wir die Methode selbst per Invoke auf
                treeView1.Invoke(new MethodInvoker(FillTreeView));
                return;
            }

            treeView1.Nodes.Clear();
            root = SearchTreeView(treeView1, "Bibliothek");
            if(root == null){
                root = treeView1.Nodes.Add("Bibliothek");
            }
            root.Tag = "Root";

            DataSet dsCDs = DataHelper.GetCDs();
           
            try
            {
                foreach (DataRow rowCD in dsCDs.Tables[0].Rows)
                {
                    int cdID = Convert.ToInt32(rowCD["ID"]);
                    TreeNode childRoot = new TreeNode(rowCD["Name"].ToString());
                    root.Nodes.Add(childRoot);
                    childRoot.ImageIndex = 1;
                    childRoot.Tag = "CD";
                    childRoot.SelectedImageIndex = 1;
                    childRoot.Parent.Expand();
                    // childRoot.ContextMenu.Show = true;

                    DataSet dsTracks = DataHelper.GetCDTracksByID(cdID);
                    foreach (DataRow rowTrack in dsTracks.Tables[0].Rows)
                    {
                        TreeNode childCD = new TreeNode("#" + rowTrack["Nr"].ToString() + " " + rowTrack["Artist"].ToString() + " - " + rowTrack["Title"].ToString());
                        childRoot.Nodes.Add(childCD);
                        childCD.ImageIndex = 2;
                        childCD.Tag = "Track";
                        childCD.SelectedImageIndex = 2;
                    }
                    dsTracks.Dispose();
                }
                dsCDs.Dispose();
            }
            catch (Exception e)
            {
                if (DebugSession == false)
                {
                    MessageBox.Show("Fehler in FillTreeView(): " + e.Message);
                }
                else
                {
                    Debug.listBox1.Items.Add("[" + DateTime.Now + "] Fehler in FillTreeView(): " + e.Message);
                }
            }
        }

Ich hab ehrlich gesagt keine Ahnung woran es liegt.
Eigentlich sollte doch durch den Thread die Form normal weiter arbeiten und nicht erst auf die Daten für den Treeview warten, oder?
 

Anhänge

  • 28.09.jpg
    28.09.jpg
    49,1 KB · Aufrufe: 13
Zuletzt bearbeitet:
Das benutze ich ja schon in meiner Funktion "FillTreeView":

Code:
 if (treeView1.InvokeRequired)
            { // Wenn Invoke nötig ist, ...
                // dann rufen wir die Methode selbst per Invoke auf
                treeView1.Invoke(new MethodInvoker(FillTreeView));
                return;
            }

An sich klappt das Threading und der Treeview füllt sich, jedoch wartet die Form dennoch darauf.
 
Hallo,

da hast du einen Designfehler in deinem Programm. Das Invoke nützt in der Situation gar nichts. Du sprichst zwar damit korrekt die GUI aus einem anderen Thread an, aber da du dabei die gesamte DB doch wieder in einem Rutsch ausliest, blockiert die GUI trotzdem.

Du solltest Invoke nur für das Erzeugen/Schreiben eines einzelnen Nodes verwenden.

Gruß
MCoder
 
Ah okay... Das heißt ich mach den Thread aus der Funktion raus und lagere das Einfügen/Schreiben der Nodes in einer Funktion aus die ich dann über einen eigenständigen Thread laufen lasse?
Leider arbeite ich noch nicht so lange mit Threads, daher stell ich mich vermutlich etwas dumm an, weil ich es halt nicht besser weiß.
Daher nicht böse sein ;)
 
Zuletzt bearbeitet:
Hallo,

die FillTree()-Methode im Thread laufen zu lassen ist ok. Allerdings solltest du nicht diese Methode invoken. Lagere das Erzeugen/Schreiben eines einzelnen Nodes - der Teil ab "TreeNode childRoot = ..." - in eine eigene Methode aus und invoke diese.

Gruß
mbmun
 
Dumme Frage, ist es nicht möglich Methoden die invoked werden Parameter zu übergeben?
Er "heult immer rum" von wegen "Method name expected" an der stelle wo ich die Funktion versuche zu invoken:

umgebaute "FilltreeView"-Methode:
Code:
private void FillTreeView()
        {
            TreeNode root;
           
            if (treeView1.InvokeRequired)
            { // Wenn Invoke nötig ist, ...
                // dann rufen wir die Methode selbst per Invoke auf
                treeView1.Invoke(new MethodInvoker(FillTreeView));
                return;
            }

            treeView1.Nodes.Clear();
            root = SearchTreeView(treeView1, "Bibliothek");
            if(root == null){
                root = treeView1.Nodes.Add("Bibliothek");
            }
            root.Tag = "Root";

            DataSet dsCDs = DataHelper.GetCDs();
           
            try
            {
                foreach (DataRow rowCD in dsCDs.Tables[0].Rows)
                {
                    int cdID = Convert.ToInt32(rowCD["ID"]);
                    DataSet dsTracks = DataHelper.GetCDTracksByID(cdID);

                    InsertNode(rowCD, root, dsTracks);

                    if (treeView1.InvokeRequired)
                    { // Wenn Invoke nötig ist, ...
                        // dann rufen wir die Methode selbst per Invoke auf
                        treeView1.Invoke(new MethodInvoker(InsertNode(rowCD,root,dsTracks));
                        return;
                    }

                    
                }
                dsCDs.Dispose();
            }
            catch (Exception e)
            {
                if (DebugSession == false)
                {
                    MessageBox.Show("Fehler in FillTreeView(): " + e.Message);
                }
                else
                {
                    Debug.listBox1.Items.Add("[" + DateTime.Now + "] Fehler in FillTreeView(): " + e.Message);
                }
            }
        }

Neue "InsertNode"-Methode:
Code:
public void InsertNode(DataRow Row, TreeNode Node, DataSet dsTracks)
        {

            TreeNode childRoot = new TreeNode(Row["Name"].ToString());
            Node.Nodes.Add(childRoot);
            childRoot.ImageIndex = 1;
            childRoot.Tag = "CD";
            childRoot.SelectedImageIndex = 1;
            childRoot.Parent.Expand();
            // childRoot.ContextMenu.Show = true;

            foreach (DataRow rowTrack in dsTracks.Tables[0].Rows)
            {
                TreeNode childCD = new TreeNode("#" + rowTrack["Nr"].ToString() + " " + rowTrack["Artist"].ToString() + " - " + rowTrack["Title"].ToString());
                childRoot.Nodes.Add(childCD);
                childCD.ImageIndex = 2;
                childCD.Tag = "Track";
                childCD.SelectedImageIndex = 2;
            }
            dsTracks.Dispose();
        }
 
Zuletzt bearbeitet:
Hallo,

das Invoke am Anfang von FillTreeView() muss natürlich raus. Bei Parametern kannst du einen selbstdefinierten Delegaten verwenden. MethodInvoker() kann das nicht.
So sollte es gehen:
C#:
private delegate void InsertNodeDelegate(DataRow Row, TreeNode Node, DataSet dsTracks);

private void InsertNode(DataRow Row, TreeNode Node, DataSet dsTracks)
{
    if( treeView1.InvokeRequired )
    {
        Invoke( new InsertNodeDelegate(InsertNode), new object [] { Row, Node, dsTracks } );
    }
    else
    {
        TreeNode childRoot = new TreeNode(Row["Name"].ToString());
        Node.Nodes.Add(childRoot);
        childRoot.ImageIndex = 1;
        childRoot.Tag = "CD";
        childRoot.SelectedImageIndex = 1;
        childRoot.Parent.Expand();
        // childRoot.ContextMenu.Show = true;
 
        foreach (DataRow rowTrack in dsTracks.Tables[0].Rows)
        {
            TreeNode childCD = new TreeNode( "#" + rowTrack["Nr"].ToString() + " " +
                                             rowTrack["Artist"].ToString() + " - " + rowTrack["Title"].ToString());
            childRoot.Nodes.Add(childCD);
            childCD.ImageIndex = 2;
            childCD.Tag = "Track";
            childCD.SelectedImageIndex = 2;
        }
        
        dsTracks.Dispose();
    }        
}

private void FillTreeView()
{
    TreeNode root;

    treeView1.Nodes.Clear();
    root = SearchTreeView(treeView1, "Bibliothek");
    
    if(root == null)
    {
        root = treeView1.Nodes.Add("Bibliothek");
    }
    
    root.Tag = "Root";
    DataSet dsCDs = DataHelper.GetCDs();
       
    try
    {
        foreach( DataRow rowCD in dsCDs.Tables[0].Rows )
        {
            int cdID = Convert.ToInt32(rowCD["ID"]);
            DataSet dsTracks = DataHelper.GetCDTracksByID(cdID);
            InsertNode(rowCD, root, dsTracks);
        }
        
        dsCDs.Dispose();
        
    catch (Exception e)
    {
        if (DebugSession == false)
        {
            MessageBox.Show("Fehler in FillTreeView(): " + e.Message);
        }
        else
        {
            Debug.listBox1.Items.Add("[" + DateTime.Now + "] Fehler in FillTreeView(): " + e.Message);
        }
    }
}
Gruß
MCoder

//edit
Das Anlegen des root-Nodes (wenn noch nicht vorhanden) solltest du auch besser außerhalb von FillTreeView() und damit des Threads machen oder du brauchst dafür auch ein Invoke.
 
Zuletzt bearbeitet:
Ah okay, dann weiß ich nun wofür ich z.b. die Delegate benutzen kann/muss. Auch wieder was gelernt.
Nun heult er aber noch in der Zeile:
Code:
 treeView1.Nodes.Clear();

Hier kommt dann diese Meldung:
Code:
Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement treeView1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

Ich rufe die Methode nach wie vor in einem eigenen Thread auf.
Lass ich das Invoke in der Methode z.B. drin, hängt die maske nach wie vor.
 
Das hatte ich vorher übersehen: Das clear() und das Anlegen des root-Nodes solltest du schon außerhalb von FillTreeView() und damit des Threads erledigen.

Gruß
MCoder
 

Neue Beiträge

Zurück