-
Nov 27th, 2007, 12:42 AM
#1
Accessing Controls from Worker Threads
C# 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:
vb.net Code:
Private Sub ResetTextBoxText() Me.TextBox1.ResetText() End Sub
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:
vb.net Code:
Private Sub ResetTextBoxText() If Me.TextBox1.InvokeRequired Then Else Me.TextBox1.ResetText() End If End Sub
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:
vb.net Code:
Private Sub ResetTextBoxText() If Me.TextBox1.InvokeRequired Then Me.TextBox1.Invoke(New MethodInvoker(AddressOf ResetTextBoxText)) Else Me.TextBox1.ResetText() End If End Sub
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 Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Threading.Thread.CurrentThread.Name = "UI Thread"
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Me.ResetTextBoxText()
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Threading.Thread.CurrentThread.Name = "Worker Thread"
Me.ResetTextBoxText()
End Sub
Private Sub ResetTextBoxText()
Debug.WriteLine(Threading.Thread.CurrentThread.Name, "ResetTextBoxText")
If Me.TextBox1.InvokeRequired Then
Me.TextBox1.Invoke(New MethodInvoker(AddressOf ResetTextBoxText))
Else
Me.TextBox1.ResetText()
End If
End Sub
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.
Last edited by jmcilhinney; Oct 28th, 2008 at 12:11 AM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|