How would i execute it in worker thread ?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • akshaycjoshi
    New Member
    • Jan 2007
    • 153

    How would i execute it in worker thread ?

    Consider the following code in which i add nodes to a treeview control by reading an MS Access database.

    Obviously, if I execute in a seperate thread I will get exception about control accessed from a thread other than the one in which it is created.

    Now whenever I am adding one node to the tree I could marshel it to the GUI thread like this
    Code:
    void addnode(string textofnode)
    {
    }
    The problem is that I have got some nodes which are parents of child nodes.
    So we create a node and add all children of it and finally add it to treeview.

    If i create the children in worker thread and send the parent to GUI thread to add it to the treeview I guess I wont be able to access those children from GUI thread(coz they are cerated in the worker thread)
    How would i get around this problem?


    Code:
                  TreeNode extensions = new TreeNode("Extensions");
                    treeexchange.Nodes.Add(extensions);
    
                    DataTable dtgroups = new DataTable("Groups"); //get groups
                    OleDbDataAdapter adp = new OleDbDataAdapter("select * from t_extensiongroups", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                    adp.Fill(dtgroups);
    
                    for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
                    {
                        //create a groupnode with the group name in it
                        TreeNode group = new TreeNode(dtgroups.Rows[i][0].ToString());
    
                        //get no of extensions corresposnding to that group
                        adp = new OleDbDataAdapter("select * from t_extensions where group_name='" + dtgroups.Rows[i]["groups"].ToString() + "'", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                        DataTable dtextensions = new DataTable("Extensions");
                        adp.Fill(dtextensions);
    
                        //add all the extension nodes to the groupnode
                        for (int j = 0; j < dtextensions.Rows.Count; j++)
                        {
                            group.Nodes.Add(dtextensions.Rows[j]["name"].ToString() + " (" + dtextensions.Rows[j]["number"].ToString() + ")");
                        }
    
                        //add the group
                        extensions.Nodes.Add(group);
                    }
    
                    //create a trunk node
                    TreeNode trunks = new TreeNode("Trunks");
    
                    //get trunks
                    adp = new OleDbDataAdapter("select connected_trunk,name from t_trunk_lines", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                    DataTable dttrunks = new DataTable("Trunks");
                    adp.Fill(dttrunks);
    
                    //add all trunks to the trunk node
                    for (int i = 0; i < dttrunks.Rows.Count; i++)
                    {
                        trunks.Nodes.Add(dttrunks.Rows[i]["name"].ToString() + " (" + dttrunks.Rows[i]["connected_trunk"].ToString() + ")");
                    }
    
                    //add the trunk node
                    treeexchange.Nodes.Add(trunks);
    
                    //add the pbx node
                    treeexchange.Nodes.Add("My PBX");
    
                    treeexchange.Nodes.Add("User");
  • mldisibio
    Recognized Expert New Member
    • Sep 2008
    • 191

    #2
    Your code is unformatted and very difficult to read.

    When updating controls on a WinForm from a thread, use a derivative of Control.Invoke to update the control.

    A very safe way to update controls on a WinForm is to spawn the thread using a BackgroundWorke r component.

    Here is a great article discussing thread updating WinForms:
    Threading in Windows Forms.

    Comment

    • akshaycjoshi
      New Member
      • Jan 2007
      • 153

      #3
      Here is the formatted code :
      Code:
                  treeexchange.Nodes.Clear();
                      TreeNode extensions = new TreeNode("Extensions");
                      treeexchange.Nodes.Add(extensions);
      
                      DataTable dtgroups = new DataTable("Groups"); //get groups
                      OleDbDataAdapter adp = new OleDbDataAdapter("select * from t_extensiongroups", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                      adp.Fill(dtgroups);
      
                      for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
                      {
                          //create a groupnode with the group name in it
                          TreeNode group = new TreeNode(dtgroups.Rows[i][0].ToString());
      
                          //get no of extensions corresposnding to that group
                          adp = new OleDbDataAdapter("select * from t_extensions where group_name='" + dtgroups.Rows[i]["groups"].ToString() + "'", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                          DataTable dtextensions = new DataTable("Extensions");
                          adp.Fill(dtextensions);
      
                          //add all the extension nodes to the groupnode
                          for (int j = 0; j < dtextensions.Rows.Count; j++)
                          {
                              group.Nodes.Add(dtextensions.Rows[j]["name"].ToString() + " (" + dtextensions.Rows[j]["number"].ToString() + ")");
                          }
      
                          //add the group
                          extensions.Nodes.Add(group);
                      }
      
                      //create a trunk node
                      TreeNode trunks = new TreeNode("Trunks");
      
                      //get trunks
                      adp = new OleDbDataAdapter("select connected_trunk,name from t_trunk_lines", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                      DataTable dttrunks = new DataTable("Trunks");
                      adp.Fill(dttrunks);
      
                      //add all trunks to the trunk node
                      for (int i = 0; i < dttrunks.Rows.Count; i++)
                      {
                          trunks.Nodes.Add(dttrunks.Rows[i]["name"].ToString() + " (" + dttrunks.Rows[i]["connected_trunk"].ToString() + ")");
                      }
      
                      //add the trunk node
                      treeexchange.Nodes.Add(trunks);
      
                      //add the pbx node
                      treeexchange.Nodes.Add("My PBX");
      
                      treeexchange.Nodes.Add("User");

      Comment

      • mldisibio
        Recognized Expert New Member
        • Sep 2008
        • 191

        #4
        I will make two suggestions, but I have not written an example to prove they will work correctly. Let me know if they do not.

        Suggestion #1:
        • Instantiate the initial TreeView in the GUI.
        • Pass it as a parameter to a BackgroundWorke r thread (also created in the GUI).
        • Have the thread fill in the nodes (your posted code).
        • Handle the RunWorkerComple ted event to update the actual display of the filled-in TreeView.
        • How To: Run an Operation in the Background.


        Suggestion #2:
        If you still receive a thread access violation after implementing the above, then add a helper method to your GUI which will clone the nodes created in the thread to the nodes in your GUI TreeView. This can be a simple recusion loop and should run quickly, since you have already performed the data access part in your thread.

        However, I truly believe the second step will not be necessary. If you spawn a BackgroundWorke r thread from the GUI, it is guaranteed to execute on the same thread as the GUI itself. It is provided in the Component namespace specifically to eliminate the cross-threading issues associated with WinForms.

        Comment

        • r035198x
          MVP
          • Sep 2006
          • 13225

          #5
          Originally posted by mldisibio
          .. If you spawn a BackgroundWorke r thread from the GUI, it is guaranteed to execute on the same thread as the GUI itself. It is provided in the Component namespace specifically to eliminate the cross-threading issues associated with WinForms.
          Actually the task you specify to DoWork is run on a separate thread. That's why the interface remains responsive. The key thing is to make sure that you don't do any GUI updates from DoWork. If intermediate GUI updates are required as in this case then the OnProgressChang ed method should be used which raises the ProgressChanged event. This allows you to queue the interface updates to the UI interface thread.

          Comment

          • IanWright
            New Member
            • Jan 2008
            • 179

            #6
            I'm guessing you want something alone the lines of :

            Code:
            delegate void AddNodeDelegate(string nodeName);
            
            void AddNode(string nodeName)
            {
               if(treeexchange.InvokeRequired)
               {
                  AddNodeDelegate addDelegate = new AddNodeDelegate(AddNode);
                  this.Invoke(addDelegate, new object[] { nodeName });
               }   
              else
              {
                 this.treeexchange.Nodes.Add(nodeName);
              }
            }
            Your alternative (which I don't like)... is to not do the work in a background thread at all. And put the following in appropriate places to refresh your GUI.

            Code:
            Application.DoEvents();

            Comment

            • mldisibio
              Recognized Expert New Member
              • Sep 2008
              • 191

              #7
              .... And yes, as you say, interface updates must be done via the RunWorkerComple ted event, and optionally the ProgressChanged event if desired.

              Comment

              • akshaycjoshi
                New Member
                • Jan 2007
                • 153

                #8
                Originally posted by mldisibio
                I will make two suggestions, but I have not written an example to prove they will work correctly. Let me know if they do not.


                Suggestion #1:
                • Instantiate the initial TreeView in the GUI.
                • Pass it as a parameter to a BackgroundWorke r thread (also created in the GUI).
                • Have the thread fill in the nodes (your posted code).
                • Handle the RunWorkerComple ted event to update the actual display of the filled-in TreeView.
                • How To: Run an Operation in the Background.
                If I add nodes in the BackgroundWorke r thread then will I be able to access them from the main GUI thread ?

                In some code i am reading the Text of the nodes.

                Comment

                • r035198x
                  MVP
                  • Sep 2006
                  • 13225

                  #9
                  Originally posted by mldisibio
                  .... And yes, as you say, interface updates must be done via the RunWorkerComple ted event, and optionally the ProgressChanged event if desired.
                  Not precisely. The RunWorkerComple ted is used to update the interface when the background task has completed while the ProgressChanged is used for updating the interface while the background task is still running. It's a way for providing intermediate results of the ask to the interface.

                  EDIT:mldisibio, I'm terribly sorry that I have messed up your response above. I accidentally clicked the edit button when I wanted to click the quote button so in the end I ended up deleting the first and last parts of your post. Only the part that I quoted now remains!
                  Please feel free to post it again if you wish.

                  Comment

                  • mldisibio
                    Recognized Expert New Member
                    • Sep 2008
                    • 191

                    #10
                    No problem. For the record I simply corrected my original post, agreeing with your response: the DoWork thread is indeed a separate thread, not the same as the UI thread, but spawing the BackgroundWorke r from the UI guarantees that the worker thread will be correctly marshalled back to the UI thread.
                    A great link on this subject from Alhahari: Threading in C# - Part 3 - Using Threads

                    Comment

                    • mldisibio
                      Recognized Expert New Member
                      • Sep 2008
                      • 191

                      #11
                      Back to the last question, and again, confirming what r035198x says: you use the RunWorkerComple ted event, which has a "Result" property, to update the UI. So in brief, your BackgroundWorke r would build the TreeView, and return it as the "Result" argument of the RunWorkerComple tedEventsArgs, and after casting it back to a TreeView, you could replace the UI TreeView with the newly created one, or clear the original TreeView and copy the nodes from the one returned by the thread.

                      At that point, assuming you use the RunWorkerComple ted event, yes, your GUI will be able to access the nodes, even though they were created by another thread, because the BackgroundWorke r knows how to handle the thread marshalling back to the UI thread correctly.

                      Comment

                      • akshaycjoshi
                        New Member
                        • Jan 2007
                        • 153

                        #12
                        I have done the updates to my program and still i am getting that exception regarding the thread.
                        Here is my (partial) code
                        Code:
                         
                        private void InitializeComponent()
                        {
                        private System.ComponentModel.BackgroundWorker bgwtreeupdater;
                        this.bgwtreeupdater.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bgwtreeupdater_DoWork);
                         
                        }
                         
                         
                        this.bgwtreeupdater = new System.ComponentModel.BackgroundWorker();
                         
                         
                        void refreshtree()
                        {
                        bgwtreeupdater.RunWorkerAsync(treeexchange);
                        }
                         
                        private void bgwtreeupdater_DoWork(object sender, DoWorkEventArgs e)
                        {
                        TreeView texchange = (TreeView)e.Argument;
                        texchange.Nodes.Clear();
                        TreeNode extensionsnode = new TreeNode("Extensions");
                        treeexchange.Nodes.Add(extensionsnode);
                        DataTable dtgroups = new DataTable("Groups"); //get groups
                        OleDbDataAdapter adp = new OleDbDataAdapter("select group_name,group_date_of_creation ,group_no_of_extensions from t_extensiongroups", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                        adp.Fill(dtgroups);
                        //when the user points to this it shows the details of the extension
                        extensionsnode.ToolTipText = "No of groups: " + dtgroups.Rows.Count.ToString();
                        int totalextensions = 0;
                        for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
                        {
                        //create a groupnode with the group name in it
                        TreeNode groupnode = new TreeNode(dtgroups.Rows[i]["group_name"].ToString());
                        groupnode.ToolTipText = "Group name: " + dtgroups.Rows[i]["group_name"].ToString();
                         
                        //get no of extensions corresposnding to that group
                        adp = new OleDbDataAdapter("select ext_name,group_name,ext_number,ext_date_of_creation from t_extensions where group_name='" + dtgroups.Rows[i]["group_name"].ToString() + "'", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                        DataTable dtextensions = new DataTable("Extensions");
                        adp.Fill(dtextensions);
                        int totalgrpextensions = 0;
                        //add all the extension nodes to the groupnode
                        //
                        for (int j = 0; j < dtextensions.Rows.Count; j++)
                        {
                        TreeNode extensionnode = new TreeNode(dtextensions.Rows[j]["ext_number"].ToString());
                        groupnode.Nodes.Add(extensionnode);
                        extensionnode.ToolTipText = "Extension name: " + dtextensions.Rows[j]["ext_name"].ToString() + "\nExtension no: " + dtextensions.Rows[j]["ext_number"].ToString() + "\nExtension group: " + dtextensions.Rows[j]["group_name"].ToString() + "\nCreated: " + dtextensions.Rows[j]["ext_date_of_creation"].ToString();
                        totalextensions++;
                        totalgrpextensions++;//for setting the tool tip groups wise
                        }
                        //add the groupnode
                        extensionsnode.Nodes.Add(groupnode);
                        //add the tooltip to this groupnode
                        groupnode.ToolTipText += "\nNo of extensions: " + totalgrpextensions.ToString() + "\n" + "Created: " + dtgroups.Rows[i]["group_date_of_creation"].ToString();
                        }
                         
                        //got all the details about the extension now fill it !
                        extensionsnode.ToolTipText += "\nNo of extensions: " + totalextensions.ToString();
                         
                        //create a trunk node
                        TreeNode trunksnode = new TreeNode("Trunks");
                        //get trunks
                        adp = new OleDbDataAdapter("select trunk_connected_trunk,trunk_name,trunk_virtual_number,trunk_date_of_creation from t_trunk_lines", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
                        DataTable dttrunks = new DataTable("Trunks");
                        adp.Fill(dttrunks);
                        //add all trunks to the "trunks" node
                        for (int i = 0; i < dttrunks.Rows.Count; i++)
                        {
                        TreeNode trunknode = new TreeNode(dttrunks.Rows[i]["trunk_virtual_number"].ToString() + " (" + dttrunks.Rows[i]["trunk_connected_trunk"].ToString() + ")");
                        trunksnode.Nodes.Add(trunknode);
                        trunknode.ToolTipText = "Trunk name: " + dttrunks.Rows[i]["trunk_name"].ToString() + "\nConnected trunk: " + dttrunks.Rows[i]["trunk_connected_trunk"].ToString() + "\nVirtual trunk no:" + dttrunks.Rows[i]["trunk_virtual_number"].ToString() + "\nCreated:" + dttrunks.Rows[i]["trunk_date_of_creation"].ToString();
                        }
                        //add the trunk node
                        treeexchange.Nodes.Add(trunksnode);
                        //add the tag to this "trunks" node
                        trunksnode.ToolTipText = "Number of trunks: " + dttrunks.Rows.Count.ToString();
                        //add the pbx node
                        TreeNode pbxnode = new TreeNode("My PBX");
                        pbxnode.ToolTipText = "My Pbx";
                        treeexchange.Nodes.Add(pbxnode);
                        //add the User node
                        TreeNode usernode = new TreeNode("User");
                        usernode.ToolTipText = "User";
                        treeexchange.Nodes.Add(usernode);
                        }
                        After all it's not the UI thread, so wont be able to access the treexchange treeview.

                        Comment

                        • mldisibio
                          Recognized Expert New Member
                          • Sep 2008
                          • 191

                          #13
                          I know in my first post I suggested passing the TreeView as an argument to DoWork, but that seems to violate what r035198x said about not updating the UI except from the RunWorkerComple ted event.

                          Try creating the TreeView in the DoWork thread, and then replacing the GUI one with the new instance passed back as e.Result in the RunWorkerComple ted EventArgs.

                          (Also, the order of your code is not clear, but make sure you do not re-create the BackgroundWorke r instance after you assign the DoWork delegate.)

                          Comment

                          • akshaycjoshi
                            New Member
                            • Jan 2007
                            • 153

                            #14
                            There is nothing like doing work and then passing the result(treeview ) to the RunWorkerComple ted.
                            The only work i need to do is fill the nodes of the treeview by reading the database;

                            I am thinking of populating an arraylist in the worker thread and then in the GUI thread make the nodes out of it and fill the treeview.

                            In case of child of nodes, since we can place anything in an arraylist, i will add one arraylist as item in the main arraylist when i need to create nodes which are children of other node.

                            Tell me.

                            Comment

                            • mldisibio
                              Recognized Expert New Member
                              • Sep 2008
                              • 191

                              #15
                              Add:
                              Code:
                              private void InitializeComponent(){
                                private BackgroundWorker bgwtreeupdater;
                                this.bgwtreeupdater = new BackgroundWorker();
                                this.bgwtreeupdater.DoWork += new DoWorkEventHandler(this.bgwtreeupdater_DoWork);
                                this.bgwtreeupdated.RunWorkerCompleted += bgwtreeupdater_RunWorkerCompleted;
                              }
                              In your DoWork delegate, make a small change:
                              Code:
                              private void bgwtreeupdater_DoWork(object sender, DoWorkEventArgs e){
                                //TreeView texchange = (TreeView)e.Argument;
                                TreeView tempTreeView = new TreeView();
                                // .. all your existing code to fill it
                                e.Result = tempTreeView;
                              }
                              Then add:
                              Code:
                              void bgwtreeupdater_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) {
                                if (!e.Cancelled && e.Error != null){
                                   TreeView tv = e.Result as TreeView;
                                   if (tv != null)
                                     this.treeexchange =  tv;
                                }
                              }
                              P.S. I notice in your posted code for DoWork you say
                              Code:
                              TreeView texchange = (TreeView) e.Argument;
                              but you start referencing "treexchang e" after that.
                              Since it is a reference object, I don't think it will solve the problem, but make sure your thread error is not because you are not referencing the argument passed in to the DoWork method.

                              Comment

                              Working...