Results 1 to 10 of 10

Thread: Accessing Controls from Worker Threads

Threaded View

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,344

    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:
    csharp Code:
    1. private void Form1_Load(object sender, EventArgs e)
    2. {
    3.     Thread.CurrentThread.Name = "UI Thread";
    4. }
    5.  
    6. private void button1_Click(object sender, EventArgs e)
    7. {
    8.     this.ResetTextBoxText();
    9.  
    10.     this.backgroundWorker1.RunWorkerAsync();
    11. }
    12.  
    13. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    14. {
    15.     Thread.CurrentThread.Name = "Worker Thread";
    16.  
    17.     this.ResetTextBoxText();
    18. }
    19.  
    20. private void ResetTextBoxText()
    21. {
    22.     Debug.WriteLine(Thread.CurrentThread.Name, "ResetTextBoxText");
    23.  
    24.     if (this.textBox1.InvokeRequired)
    25.     {
    26.         this.textBox1.Invoke(new MethodInvoker(ResetTextBoxText));
    27.     }
    28.     else
    29.     {
    30.         this.textBox1.ResetText();
    31.     }
    32. }
    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.

Posting Permissions

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



Click Here to Expand Forum to Full Width