Accessing Controls from Worker Threads-VBForums
Results 1 to 9 of 9

Thread: Accessing Controls from Worker Threads

Hybrid View

  1. #1

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    87,669

    Accessing Controls from Worker Threads

    VB version here.

    While it is legal to access a control from threads other than the one it was created on, it is simply not a good idea. From VS 2005 onwards it is not allowed while debugging by default. Any call that accesses a control's Handle on a thread that does not own that handle will either fail or behave other than as expected. The way to safely access controls from worker threads is via delegation.

    First you test the InvokeRequired property of the control, which will tell you whether or not you can safely access the control. InvokeRequired is one of the few members of the Control class that is thread-safe, so you can access it anywhere. If the property is True then an invocation is required to access the control because the current method is executing on a thread other than the one that owns the control's Handle.

    The invocation is performed by calling the control's Invoke or BeginInvoke method. You create a delegate, which is an object that contains a reference to a method. It is good practice to make that a reference to the current method. You then pass that delegate to the Invoke or BeginInvoke method. That will essentially call the referenced method again, this time on the thread that owns the control's Handle.

    So, the first step is to identify the control member you want to access. For a first example, let's use the ResetText method of a TextBox. That's nice and simple because there are no parameters and no return value. When starting out you can build up your solution in stages. The first stage is to write a simple method that accesses your control member as desired:
    CSharp Code:
    1. private void ResetTextBoxText()
    2. {
    3.     this.textBox1.ResetText();
    4. }
    Nice and easy. Ordinarily you could simply call that method and it would do what you wanted: reset the TextBox's Text property.

    Now, the next stage is to test the control's InvokeRequired property and place the existing method body in the Else block:
    CSharp Code:
    1. private void ResetTextBoxText()
    2. {
    3.     if (this.textBox1.InvokeRequired)
    4.     {
    5.  
    6.     }
    7.     else
    8.     {
    9.         this.textBox1.ResetText();
    10.     }
    11. }
    That now says that if an invocation is NOT required we can access the control member directly.

    The next stage is to create the delegate and use it to invoke the current method on the appropriate thread. As I said, our method has no parameters and no return value so it's nice and easy. In that case you can create an instance of the existing MethodInvoker delegate:
    CSharp Code:
    1. private void ResetTextBoxText()
    2. {
    3.     if (this.textBox1.InvokeRequired)
    4.     {
    5.         this.textBox1.Invoke(new MethodInvoker(ResetTextBoxText));
    6.     }
    7.     else
    8.     {
    9.         this.textBox1.ResetText();
    10.     }
    11. }
    If an invocation is required, this creates a MethodInvoker delegate and passes it a reference to the current method. That delegate is then passed to the control's Invoke method, which carries it across the thread boundary to the thread that owns the control's Handle and invokes it. To invoke a delegate means to execute the method it has a reference to.

    So now you can simply call that method from any thread you like and rest assured that if you're not on the correct thread that the method itself will handle it for you. To see this in action, try creating a new project and add a Button and a TextBox to the form along with a BackgroundWorker. Now add the following code:
    Code:
    private void Form1_Load(object sender, EventArgs e)
    {
        Thread.CurrentThread.Name = "UI Thread";
    }
    
    private void button1_Click(object sender, EventArgs e)
    {
        this.ResetTextBoxText();
    
        this.backgroundWorker1.RunWorkerAsync();
    }
    
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.CurrentThread.Name = "Worker Thread";
    
        this.ResetTextBoxText();
    }
    
    private void ResetTextBoxText()
    {
        Debug.WriteLine(Thread.CurrentThread.Name, "ResetTextBoxText");
    
        if (this.textBox1.InvokeRequired)
        {
            this.textBox1.Invoke(new MethodInvoker(ResetTextBoxText));
        }
        else
        {
            this.textBox1.ResetText();
        }
    }
    Use the Properties window in the designer to attach the three event handlers to the appropriate events.

    Now run the program, click the Button and watch the Output window. You'll see that the ResetTextBoxText method is executed three times. The first time is in the UI thread when it's called from the Button's Click event handler. The second time is in the worker thread when it's called from the DoWork event handler. The third time is in the UI thread again, when it's invoked via the delegate.

    EDIT: I should also point out the difference between Invoke and BeginInvoke. Invoke is a synchronous method. That means that when you call it the background thread will block until the method invoked on the UI thread completes. BeginInvoke is asynchronous, so it will return immediately and the background thread will continue on its way while the method invoked on the UI thread executes.

    2007-2014

    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts) | WP8 Turnstile Feather Transition with Pivot Control
    Beginner Tutorials: VB | C# | SQL

  2. #2

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    87,669

    Re: Accessing Controls from Worker Threads

    To invoke a method via a delegate, the delegate must have the same signature as the method. The example in the first post invokes a method that has no parameters and no return value. In that case you can use the existing MethodInvoker delegate. If you need to specify one or more parameters or get a return value though, you will have to declare your own delegate type.

    Let's say that you want to set the Text property of a TextBox from a worker thread. From the first post we know the first step is to declare a method that does what we want:
    CSharp Code:
    1. private void SetTextBoxText(string text)
    2. {
    3.     this.textBox1.Text = text;
    4. }
    Now, that method has a parameter so we cannot use the MethodInvoker delegate. We must declare our own delegate type with the same signature. The signature is basically the parameter list and the return type. To create your delegate declaration you should copy the method declaration:
    CSharp Code:
    1. private void SetTextBoxText(string text);
    then add the 'delegate' key word:
    CSharp Code:
    1. private delegate void SetTextBoxText(string text);
    and finally change the name:
    CSharp Code:
    1. private delegate void SetTextBoxTextInvoker(string text);
    Now that we have declared our new delegate type we can create instances of it just as we did with the MethodInvoker.

    The next step from above was to add a test for the InvokeRequired property of our control:
    CSharp Code:
    1. private void SetTextBoxText(string text)
    2. {
    3.     if (this.textBox1.InvokeRequired)
    4.     {
    5.  
    6.     }
    7.     else
    8.     {
    9.         this.textBox1.Text = text;
    10.     }
    11. }
    Finally we call the control's Invoke method and pass our delegate:
    CSharp Code:
    1. private void SetTextBoxText(string text)
    2. {
    3.     if (this.textBox1.InvokeRequired)
    4.     {
    5.         this.textBox1.Invoke(new SetTextBoxTextInvoker(SetTextBoxText),
    6.                              text);
    7.     }
    8.     else
    9.     {
    10.         this.textBox1.Text = text;
    11.     }
    12. }
    Notice that, while last time the only parameter passed to the Invoke method was the delegate, this time we pass the 'text' value as well. Any parameters passed to Invoke after the delegate will then be passed again to the method that gets invoked by the delegate. Our delegate will be invoking the SetTextBoxText method, which requires a 'text' parameter. That code gets the 'text' value passed in to the first call and propagates it to the second call via the Invoke method and the delegate.

    The above example passes a single parameter but the very same mechanism can be used to pass multiple parameters. Here's a similar example that can be used to set the Text property of any control:
    CSharp Code:
    1. private delegate void SetControlTextInvoker(Control ctl, string text);
    2.  
    3. private void SetControlText(Control ctl, string text)
    4. {
    5.     if (ctl.InvokeRequired)
    6.     {
    7.         ctl.Invoke(new SetControlTextInvoker(SetControlText),
    8.                    ctl,
    9.                    text);
    10.     }
    11.     else
    12.     {
    13.         ctl.Text = text;
    14.     }
    15. }
    Pass in the control and the text and the method handles the rest. It tests the InvokeRequired property and calls the Invoke method of the appropriate control, then sets the Text property of that same control.


    EDIT: A note for those using .NET 1.x. The Control.Invoke method signature changed in .NET 2.0 to accept any number of individual parameters. Previous versions required any parameters to be passed within an array. That means that while this is fine in .NET 2.0 and above:
    CSharp Code:
    1. ctl.Invoke(new SetControlTextInvoker(SetControlText),
    2.            ctl,
    3.            text);
    you would have to do this in .NET 1.x:
    vb.net Code:
    1. ctl.Invoke(new SetControlTextInvoker(SetControlText),
    2.            new object[] {ctl,
    3.                          text});
    Last edited by jmcilhinney; Oct 30th, 2008 at 06:29 PM.

    2007-2014

    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts) | WP8 Turnstile Feather Transition with Pivot Control
    Beginner Tutorials: VB | C# | SQL

  3. #3

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    87,669

    Re: Accessing Controls from Worker Threads

    Getting a return value is no more difficult. One thing I haven't mentioned is that the Invoke method is a function. If the method invoked by the delegate returns a value then that is propagated by the Invoke method. Let's use an example of getting the Text property of a TextBox. Following the steps laid out already we first create a method to do the job:
    CSharp Code:
    1. private string GetTextBoxText()
    2. {
    3.     return this.textBox1.Text;
    4. }
    Next we declare a matching delegate:
    CSharp Code:
    1. private delegate string GetTextBoxTextInvoker();
    2.  
    3. private string GetTextBoxText()
    4. {
    5.     return this.textBox1.Text;
    6. }
    After that we test the InvokeRequired property:
    CSharp Code:
    1. private delegate string GetTextBoxTextInvoker();
    2.  
    3. private string GetTextBoxText()
    4. {
    5.     if (this.textBox1.InvokeRequired)
    6.     {
    7.  
    8.     }
    9.     else
    10.     {
    11.         return this.textBox1.Text;
    12.     }
    13. }
    Finally we call the Invoke method and pass an instance of our delegate:
    CSharp Code:
    1. private delegate string GetTextBoxTextInvoker();
    2.  
    3. private string GetTextBoxText()
    4. {
    5.     if (this.textBox1.InvokeRequired)
    6.     {
    7.         return (string)this.textBox1.Invoke(new GetTextBoxTextInvoker(GetTextBoxText));
    8.     }
    9.     else
    10.     {
    11.         return this.textBox1.Text;
    12.     }
    13. }
    Let's clean that up a little so we only have one Return statement, which is widely considered to be best practice:
    CSharp Code:
    1. private delegate string GetTextBoxTextInvoker();
    2.  
    3. private string GetTextBoxText()
    4. {
    5.     string text;
    6.  
    7.     if (this.textBox1.InvokeRequired)
    8.     {
    9.         text = (string)this.textBox1.Invoke(new GetTextBoxTextInvoker(GetTextBoxText));
    10.     }
    11.     else
    12.     {
    13.         text = this.textBox1.Text;
    14.     }
    15.  
    16.     return text;
    17. }
    Notice that in this case we are actually returning the result of the Invoke method, which is the same value as was returned by the method that was invoked. This is how you get a value back onto your worker thread from the UI thread.

    Notice also that the return value from the invoke method must be cast as the appropriate type. Invoke can be used to invoke any method at all, so it could return any value at all. That means that its actual return type is Object. You must therefore cast each returned object as its actual type.

    Now to show that this method can be generalised to any control too, as well as combine the passing of parameters and returning a value, here's an extended example based on what we've already seen:
    CSharp Code:
    1. private delegate string GetControlTextInvoker(Control ctl);
    2.  
    3. private string GetControlText(Control ctl)
    4. {
    5.     string text;
    6.  
    7.     if (ctl.InvokeRequired)
    8.     {
    9.         text = (string)ctl.Invoke(new GetControlTextInvoker(GetControlText),
    10.                                   ctl);
    11.     }
    12.     else
    13.     {
    14.         text = ctl.Text;
    15.     }
    16.  
    17.     return text;
    18. }

    2007-2014

    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts) | WP8 Turnstile Feather Transition with Pivot Control
    Beginner Tutorials: VB | C# | SQL

  4. #4
    Addicted Member
    Join Date
    Sep 2008
    Location
    Jacksonville, Florida
    Posts
    147

    Re: Accessing Controls from Worker Threads

    This extension takes care of the checking for invoke required part

    Code:
    public static void InvokeSafe<T>(this T c, Action<T> DoWhat)
    	where T : System.Windows.Forms.Control
    		{
    			 
    			Action d = delegate() { DoWhat(c); };
    			if (c.InvokeRequired)
    				c.Invoke(d);
    			else
    				DoWhat(c);
    
    		 
    
    		}
    vb.net and C# in 2008 .net 3.5
    ImaginaryDevelopment blog

  5. #5

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    87,669

    Re: Accessing Controls from Worker Threads

    Here's the last example from post #1 redone for WPF:
    csharp Code:
    1. private BackgroundWorker worker = new BackgroundWorker();
    2.  
    3. private void Window_Loaded(object sender, RoutedEventArgs e)
    4. {
    5.     Thread.CurrentThread.Name = "UI Thread";
    6.  
    7.     this.worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    8. }
    9.  
    10. private void button1_Click(object sender, RoutedEventArgs e)
    11. {
    12.     this.ResetTextBoxText();
    13.  
    14.     this.worker.RunWorkerAsync();
    15. }
    16.  
    17. private void worker_DoWork(object sender, DoWorkEventArgs e)
    18. {
    19.     Thread.CurrentThread.Name = "Worker Thread";
    20.  
    21.     this.ResetTextBoxText();
    22. }
    23. private void ResetTextBoxText()
    24. {
    25.     Debug.WriteLine(Thread.CurrentThread.Name, "ResetTextBoxText");
    26.  
    27.     if (this.textBox1.Dispatcher.Thread == Thread.CurrentThread)
    28.     {
    29.         this.textBox1.Clear();
    30.     }
    31.     else
    32.     {
    33.         this.textBox1.Dispatcher.Invoke(new Action(ResetTextBoxText));
    34.     }
    35. }

    2007-2014

    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts) | WP8 Turnstile Feather Transition with Pivot Control
    Beginner Tutorials: VB | C# | SQL

  6. #6
    Lively Member
    Join Date
    Feb 2008
    Posts
    107

    Thumbs up Re: Accessing Controls from Worker Threads

    Very use full once i started reading it, makes perfect sense thanks for the learning materials.

  7. #7
    Fanatic Member
    Join Date
    Nov 2003
    Posts
    725

    Re: Accessing Controls from Worker Threads

    Hmmm .. but ToolStripProgressBar does not have Invoke and or InvokeRequired ?

    please advise

    thanks

  8. #8

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    87,669

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by Winanjaya View Post
    Hmmm .. but ToolStripProgressBar does not have Invoke and or InvokeRequired ?

    please advise

    thanks
    The ToolStripProgressBar class inherits the ToolStripControlHost class. The ToolStripControlHost class itself has a Control property, which exposes the control being hosted. That allows you to host any control at all in a ToolStrip. Classes derived from ToolStripControlHost generally add another property that exposes a specific type of control as its own type. In that vein, the ToolStripProgressBar class has a ProgressBar property that exposes the ProgressBar control being hosted. You can get that ProgressBar control from that property and use its Invoke and InvokeRequired members.

    Alternatively, you can just use the form's Invoke and InvokeRequired members. It really doesn't matter which control you use. The purpose of the InvokeRequired property is to determine whether you're currently on the thread that owns a control's handle. The purpose of the Invoke method is to take you to that thread and execute a method. If all your controls are created on the main thread then all Invoke and InvokeRequired members will have exactly the same effect.

    2007-2014

    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts) | WP8 Turnstile Feather Transition with Pivot Control
    Beginner Tutorials: VB | C# | SQL

  9. #9
    New Member
    Join Date
    Aug 2011
    Posts
    13

    Re: Accessing Controls from Worker Threads

    Hmmm.. now everything is simple and clear)) thanx

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.