By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
424,972 Members | 1,092 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 424,972 IT Pros & Developers. It's quick & easy.

How would i execute it in worker thread ?

100+
P: 153
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
Expand|Select|Wrap|Line Numbers
  1. void addnode(string textofnode)
  2. {
  3. }
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?


Expand|Select|Wrap|Line Numbers
  1.               TreeNode extensions = new TreeNode("Extensions");
  2.                 treeexchange.Nodes.Add(extensions);
  3.  
  4.                 DataTable dtgroups = new DataTable("Groups"); //get groups
  5.                 OleDbDataAdapter adp = new OleDbDataAdapter("select * from t_extensiongroups", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
  6.                 adp.Fill(dtgroups);
  7.  
  8.                 for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
  9.                 {
  10.                     //create a groupnode with the group name in it
  11.                     TreeNode group = new TreeNode(dtgroups.Rows[i][0].ToString());
  12.  
  13.                     //get no of extensions corresposnding to that group
  14.                     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");
  15.                     DataTable dtextensions = new DataTable("Extensions");
  16.                     adp.Fill(dtextensions);
  17.  
  18.                     //add all the extension nodes to the groupnode
  19.                     for (int j = 0; j < dtextensions.Rows.Count; j++)
  20.                     {
  21.                         group.Nodes.Add(dtextensions.Rows[j]["name"].ToString() + " (" + dtextensions.Rows[j]["number"].ToString() + ")");
  22.                     }
  23.  
  24.                     //add the group
  25.                     extensions.Nodes.Add(group);
  26.                 }
  27.  
  28.                 //create a trunk node
  29.                 TreeNode trunks = new TreeNode("Trunks");
  30.  
  31.                 //get trunks
  32.                 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");
  33.                 DataTable dttrunks = new DataTable("Trunks");
  34.                 adp.Fill(dttrunks);
  35.  
  36.                 //add all trunks to the trunk node
  37.                 for (int i = 0; i < dttrunks.Rows.Count; i++)
  38.                 {
  39.                     trunks.Nodes.Add(dttrunks.Rows[i]["name"].ToString() + " (" + dttrunks.Rows[i]["connected_trunk"].ToString() + ")");
  40.                 }
  41.  
  42.                 //add the trunk node
  43.                 treeexchange.Nodes.Add(trunks);
  44.  
  45.                 //add the pbx node
  46.                 treeexchange.Nodes.Add("My PBX");
  47.  
  48.                 treeexchange.Nodes.Add("User");
Jan 6 '09 #1
Share this Question
Share on Google+
16 Replies


Expert 100+
P: 190
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 BackgroundWorker component.

Here is a great article discussing thread updating WinForms:
Threading in Windows Forms.
Jan 6 '09 #2

100+
P: 153
Here is the formatted code :
Expand|Select|Wrap|Line Numbers
  1.             treeexchange.Nodes.Clear();
  2.                 TreeNode extensions = new TreeNode("Extensions");
  3.                 treeexchange.Nodes.Add(extensions);
  4.  
  5.                 DataTable dtgroups = new DataTable("Groups"); //get groups
  6.                 OleDbDataAdapter adp = new OleDbDataAdapter("select * from t_extensiongroups", @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Application.StartupPath + @"\exchangesandtrunks.mdb;Persist Security Info=False");
  7.                 adp.Fill(dtgroups);
  8.  
  9.                 for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
  10.                 {
  11.                     //create a groupnode with the group name in it
  12.                     TreeNode group = new TreeNode(dtgroups.Rows[i][0].ToString());
  13.  
  14.                     //get no of extensions corresposnding to that group
  15.                     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");
  16.                     DataTable dtextensions = new DataTable("Extensions");
  17.                     adp.Fill(dtextensions);
  18.  
  19.                     //add all the extension nodes to the groupnode
  20.                     for (int j = 0; j < dtextensions.Rows.Count; j++)
  21.                     {
  22.                         group.Nodes.Add(dtextensions.Rows[j]["name"].ToString() + " (" + dtextensions.Rows[j]["number"].ToString() + ")");
  23.                     }
  24.  
  25.                     //add the group
  26.                     extensions.Nodes.Add(group);
  27.                 }
  28.  
  29.                 //create a trunk node
  30.                 TreeNode trunks = new TreeNode("Trunks");
  31.  
  32.                 //get trunks
  33.                 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");
  34.                 DataTable dttrunks = new DataTable("Trunks");
  35.                 adp.Fill(dttrunks);
  36.  
  37.                 //add all trunks to the trunk node
  38.                 for (int i = 0; i < dttrunks.Rows.Count; i++)
  39.                 {
  40.                     trunks.Nodes.Add(dttrunks.Rows[i]["name"].ToString() + " (" + dttrunks.Rows[i]["connected_trunk"].ToString() + ")");
  41.                 }
  42.  
  43.                 //add the trunk node
  44.                 treeexchange.Nodes.Add(trunks);
  45.  
  46.                 //add the pbx node
  47.                 treeexchange.Nodes.Add("My PBX");
  48.  
  49.                 treeexchange.Nodes.Add("User");
Jan 7 '09 #3

Expert 100+
P: 190
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 BackgroundWorker thread (also created in the GUI).
  • Have the thread fill in the nodes (your posted code).
  • Handle the RunWorkerCompleted 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 BackgroundWorker 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.
Jan 7 '09 #4

10K+
P: 13,264
@mldisibio
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 OnProgressChanged method should be used which raises the ProgressChanged event. This allows you to queue the interface updates to the UI interface thread.
Jan 8 '09 #5

100+
P: 179
I'm guessing you want something alone the lines of :

Expand|Select|Wrap|Line Numbers
  1. delegate void AddNodeDelegate(string nodeName);
  2.  
  3. void AddNode(string nodeName)
  4. {
  5.    if(treeexchange.InvokeRequired)
  6.    {
  7.       AddNodeDelegate addDelegate = new AddNodeDelegate(AddNode);
  8.       this.Invoke(addDelegate, new object[] { nodeName });
  9.    }   
  10.   else
  11.   {
  12.      this.treeexchange.Nodes.Add(nodeName);
  13.   }
  14. }
  15.  
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.

Expand|Select|Wrap|Line Numbers
  1. Application.DoEvents();
Jan 8 '09 #6

Expert 100+
P: 190
.... And yes, as you say, interface updates must be done via the RunWorkerCompleted event, and optionally the ProgressChanged event if desired.
Jan 8 '09 #7

100+
P: 153
@mldisibio
If I add nodes in the BackgroundWorker 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.
Jan 9 '09 #8

10K+
P: 13,264
.... And yes, as you say, interface updates must be done via the RunWorkerCompleted event, and optionally the ProgressChanged event if desired.
Not precisely. The RunWorkerCompleted 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.
Jan 9 '09 #9

Expert 100+
P: 190
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 BackgroundWorker 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
Jan 9 '09 #10

Expert 100+
P: 190
Back to the last question, and again, confirming what r035198x says: you use the RunWorkerCompleted event, which has a "Result" property, to update the UI. So in brief, your BackgroundWorker would build the TreeView, and return it as the "Result" argument of the RunWorkerCompletedEventsArgs, 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 RunWorkerCompleted event, yes, your GUI will be able to access the nodes, even though they were created by another thread, because the BackgroundWorker knows how to handle the thread marshalling back to the UI thread correctly.
Jan 9 '09 #11

100+
P: 153
I have done the updates to my program and still i am getting that exception regarding the thread.
Here is my (partial) code
Expand|Select|Wrap|Line Numbers
  1.  
  2. private void InitializeComponent()
  3. {
  4. private System.ComponentModel.BackgroundWorker bgwtreeupdater;
  5. this.bgwtreeupdater.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bgwtreeupdater_DoWork);
  6.  
  7. }
  8.  
  9.  
  10. this.bgwtreeupdater = new System.ComponentModel.BackgroundWorker();
  11.  
  12.  
  13. void refreshtree()
  14. {
  15. bgwtreeupdater.RunWorkerAsync(treeexchange);
  16. }
  17.  
  18. private void bgwtreeupdater_DoWork(object sender, DoWorkEventArgs e)
  19. {
  20. TreeView texchange = (TreeView)e.Argument;
  21. texchange.Nodes.Clear();
  22. TreeNode extensionsnode = new TreeNode("Extensions");
  23. treeexchange.Nodes.Add(extensionsnode);
  24. DataTable dtgroups = new DataTable("Groups"); //get groups
  25. 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");
  26. adp.Fill(dtgroups);
  27. //when the user points to this it shows the details of the extension
  28. extensionsnode.ToolTipText = "No of groups: " + dtgroups.Rows.Count.ToString();
  29. int totalextensions = 0;
  30. for (int i = 0; i < dtgroups.Rows.Count; i++)//for all the groups
  31. {
  32. //create a groupnode with the group name in it
  33. TreeNode groupnode = new TreeNode(dtgroups.Rows[i]["group_name"].ToString());
  34. groupnode.ToolTipText = "Group name: " + dtgroups.Rows[i]["group_name"].ToString();
  35.  
  36. //get no of extensions corresposnding to that group
  37. 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");
  38. DataTable dtextensions = new DataTable("Extensions");
  39. adp.Fill(dtextensions);
  40. int totalgrpextensions = 0;
  41. //add all the extension nodes to the groupnode
  42. //
  43. for (int j = 0; j < dtextensions.Rows.Count; j++)
  44. {
  45. TreeNode extensionnode = new TreeNode(dtextensions.Rows[j]["ext_number"].ToString());
  46. groupnode.Nodes.Add(extensionnode);
  47. 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();
  48. totalextensions++;
  49. totalgrpextensions++;//for setting the tool tip groups wise
  50. }
  51. //add the groupnode
  52. extensionsnode.Nodes.Add(groupnode);
  53. //add the tooltip to this groupnode
  54. groupnode.ToolTipText += "\nNo of extensions: " + totalgrpextensions.ToString() + "\n" + "Created: " + dtgroups.Rows[i]["group_date_of_creation"].ToString();
  55. }
  56.  
  57. //got all the details about the extension now fill it !
  58. extensionsnode.ToolTipText += "\nNo of extensions: " + totalextensions.ToString();
  59.  
  60. //create a trunk node
  61. TreeNode trunksnode = new TreeNode("Trunks");
  62. //get trunks
  63. 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");
  64. DataTable dttrunks = new DataTable("Trunks");
  65. adp.Fill(dttrunks);
  66. //add all trunks to the "trunks" node
  67. for (int i = 0; i < dttrunks.Rows.Count; i++)
  68. {
  69. TreeNode trunknode = new TreeNode(dttrunks.Rows[i]["trunk_virtual_number"].ToString() + " (" + dttrunks.Rows[i]["trunk_connected_trunk"].ToString() + ")");
  70. trunksnode.Nodes.Add(trunknode);
  71. 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();
  72. }
  73. //add the trunk node
  74. treeexchange.Nodes.Add(trunksnode);
  75. //add the tag to this "trunks" node
  76. trunksnode.ToolTipText = "Number of trunks: " + dttrunks.Rows.Count.ToString();
  77. //add the pbx node
  78. TreeNode pbxnode = new TreeNode("My PBX");
  79. pbxnode.ToolTipText = "My Pbx";
  80. treeexchange.Nodes.Add(pbxnode);
  81. //add the User node
  82. TreeNode usernode = new TreeNode("User");
  83. usernode.ToolTipText = "User";
  84. treeexchange.Nodes.Add(usernode);
  85. }
  86.  
After all it's not the UI thread, so wont be able to access the treexchange treeview.
Jan 10 '09 #12

Expert 100+
P: 190
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 RunWorkerCompleted 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 RunWorkerCompleted EventArgs.

(Also, the order of your code is not clear, but make sure you do not re-create the BackgroundWorker instance after you assign the DoWork delegate.)
Jan 10 '09 #13

100+
P: 153
There is nothing like doing work and then passing the result(treeview) to the RunWorkerCompleted.
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.
Jan 10 '09 #14

Expert 100+
P: 190
Add:
Expand|Select|Wrap|Line Numbers
  1. private void InitializeComponent(){
  2.   private BackgroundWorker bgwtreeupdater;
  3.   this.bgwtreeupdater = new BackgroundWorker();
  4.   this.bgwtreeupdater.DoWork += new DoWorkEventHandler(this.bgwtreeupdater_DoWork);
  5.   this.bgwtreeupdated.RunWorkerCompleted += bgwtreeupdater_RunWorkerCompleted;
  6. }
In your DoWork delegate, make a small change:
Expand|Select|Wrap|Line Numbers
  1. private void bgwtreeupdater_DoWork(object sender, DoWorkEventArgs e){
  2.   //TreeView texchange = (TreeView)e.Argument;
  3.   TreeView tempTreeView = new TreeView();
  4.   // .. all your existing code to fill it
  5.   e.Result = tempTreeView;
  6. }
Then add:
Expand|Select|Wrap|Line Numbers
  1. void bgwtreeupdater_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e) {
  2.   if (!e.Cancelled && e.Error != null){
  3.      TreeView tv = e.Result as TreeView;
  4.      if (tv != null)
  5.        this.treeexchange =  tv;
  6.   }
  7. }
P.S. I notice in your posted code for DoWork you say
Expand|Select|Wrap|Line Numbers
  1. TreeView texchange = (TreeView) e.Argument;
but you start referencing "treexchange" 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.
Jan 12 '09 #15

Expert 100+
P: 190
OK. I worked up a sample to prove that this will work and it does.
First, forget any suggestion I made about passing the TreeView in as an argument to the DoWork delegate, because as has been seen, it will generate the cross-thread error.

If you follow the last suggestion to simply create a new TreeView in your thread, store it as "e.Result" at the end of DoWork, and create a RunWorkerCompleted event handler, your code should work. One small change: obviously your initial Form has set up the TreeView graphically as you want, so you do not want to completely replace the UI TreeView with a new instance. Rather, you would just want to copy the Nodes over, which can be done like this:


Expand|Select|Wrap|Line Numbers
  1.  
  2. void bgwtreeupdater_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e){
  3.   this.treeexchange.Nodes.Clear();
  4.   if (!e.Cancelled && e.Error != null){ 
  5.      TreeView tmpTreeView = e.Result as TreeView; 
  6.      if (tmpTreeView != null){
  7.        for(int i = 0; i< tmpTreeView.Nodes.Count; i++) {
  8.          this.treeexchange.Nodes.Insert(i, (TreeNode)tmpTreeView.Nodes[i].Clone());
  9.       }
  10.    }
  11. }
  12.  
Jan 12 '09 #16

100+
P: 153
Ty so much, mldisibio :)
It worked.
Jan 14 '09 #17

Post your reply

Sign in to post your reply or Sign up for a free account.