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

Thread: Accessing Controls from Worker Threads

  1. #1

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

    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:
    1. Private Sub ResetTextBoxText()
    2.     Me.TextBox1.ResetText()
    3. 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:
    1. Private Sub ResetTextBoxText()
    2.     If Me.TextBox1.InvokeRequired Then
    3.  
    4.     Else
    5.         Me.TextBox1.ResetText()
    6.     End If
    7. 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:
    1. Private Sub ResetTextBoxText()
    2.     If Me.TextBox1.InvokeRequired Then
    3.         Me.TextBox1.Invoke(New MethodInvoker(AddressOf ResetTextBoxText))
    4.     Else
    5.         Me.TextBox1.ResetText()
    6.     End If
    7. 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.

    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,266

    Passing Parameters via Delegation

    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:
    vb.net Code:
    1. Private Sub SetTextBoxText(ByVal text As String)
    2.     Me.TextBox1.Text = text
    3. End Sub
    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:
    vb.net Code:
    1. Private Sub SetTextBoxText(ByVal text As String)
    then add the 'Delegate' key word:
    vb.net Code:
    1. Private Delegate Sub SetTextBoxText(ByVal text As String)
    and finally change the name:
    vb.net Code:
    1. Private Delegate Sub SetTextBoxTextInvoker(ByVal text As String)
    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:
    vb.net Code:
    1. Private Sub SetTextBoxText(ByVal text As String)
    2.     If Me.TextBox1.InvokeRequired Then
    3.  
    4.     Else
    5.         Me.TextBox1.Text = text
    6.     End If
    7. End Sub
    Finally we call the control's Invoke method and pass our delegate:
    vb.net Code:
    1. Private Sub SetTextBoxText(ByVal text As String)
    2.     If Me.TextBox1.InvokeRequired Then
    3.         Me.TextBox1.Invoke(New SetTextBoxTextInvoker(AddressOf SetTextBoxText), _
    4.                            text)
    5.     Else
    6.         Me.TextBox1.Text = text
    7.     End If
    8. End Sub
    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:
    vb.net Code:
    1. Private Delegate Sub SetControlTextInvoker(ByVal ctl As Control, ByVal text As String)
    2.  
    3. Private Sub SetControlText(ByVal ctl As Control, ByVal text As String)
    4.     If ctl.InvokeRequired Then
    5.         ctl.Invoke(New SetControlTextInvoker(AddressOf SetControlText), _
    6.                    ctl, _
    7.                    text)
    8.     Else
    9.         ctl.Text = text
    10.     End If
    11. End Sub
    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:
    vb.net Code:
    1. ctl.Invoke(New SetControlTextInvoker(AddressOf SetControlText), _
    2.            ctl, _
    3.            text)
    you would have to do this in .NET 1.x:
    vb.net Code:
    1. ctl.Invoke(New SetControlTextInvoker(AddressOf SetControlText), _
    2.            New Object() {ctl, _
    3.                          text})
    Last edited by jmcilhinney; Dec 2nd, 2007 at 03:56 AM.

    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,266

    Getting Return Values

    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:
    vb.net Code:
    1. Private Function GetTextBoxText() As String
    2.     Return Me.TextBox1.Text
    3. End Function
    Next we declare a matching delegate:
    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3. Private Function GetTextBoxText() As String
    4.     Return Me.TextBox1.Text
    5. End Function
    After that we test the InvokeRequired property:
    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3. Private Function GetTextBoxText() As String
    4.     If Me.TextBox1.InvokeRequired Then
    5.  
    6.     Else
    7.         Return Me.TextBox1.Text
    8.     End If
    9. End Function
    Finally we call the Invoke method and pass an instance of our delegate:
    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3. Private Function GetTextBoxText() As String
    4.     If Me.TextBox1.InvokeRequired Then
    5.         Return CStr(Me.TextBox1.Invoke(New GetTextBoxTextInvoker(AddressOf GetTextBoxText)))
    6.     Else
    7.         Return Me.TextBox1.Text
    8.     End If
    9. End Function
    Let's clean that up a little so we only have one Return statement, which is widely considered to be best practice:
    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3. Private Function GetTextBoxText() As String
    4.     Dim text As String
    5.  
    6.     If Me.TextBox1.InvokeRequired Then
    7.         text = CStr(Me.TextBox1.Invoke(New GetTextBoxTextInvoker(AddressOf GetTextBoxText)))
    8.     Else
    9.         text = Me.TextBox1.Text
    10.     End If
    11.  
    12.     Return text
    13. End Function
    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:
    vb.net Code:
    1. Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String
    2.  
    3. Private Function GetControlText(ByVal ctl As Control) As String
    4.     Dim text As String
    5.  
    6.     If ctl.InvokeRequired Then
    7.         text = CStr(ctl.Invoke(New GetControlTextInvoker(AddressOf GetControlText), _
    8.                                ctl))
    9.     Else
    10.         text = ctl.Text
    11.     End If
    12.  
    13.     Return text
    14. End Function

    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
    PowerPoster JuggaloBrotha's Avatar
    Join Date
    Sep 2005
    Location
    Lansing, MI; USA
    Posts
    3,817

    Re: Accessing Controls from Worker Threads

    This is a far better explanation than anything else I've seen

    Thanks JM

  5. #5
    Fanatic Member
    Join Date
    Sep 2005
    Location
    Toledo, OH
    Posts
    785

    Re: Accessing Controls from Worker Threads

    JM is, so far, the most detailed individual who offers help to those in need of something... so he's not only giving you the answer to your problem, but is in fact giving you the explanation of why it happened and what not..

    thank you JM

    note: all you other individuals who help, you all do a great job, keep it up.

  6. #6
    Addicted Member
    Join Date
    Mar 2008
    Posts
    150

    Re: Accessing Controls from Worker Threads

    Are there any tricks in getting the Debug output?

    I've got the output window open, tried enabling the other options than just 'Program Output' (which seems to be all i really need anyway) and 'Exception Messages'. I also tried changing the code from Debug.WriteLine to Debug.Write etc but didn't make any difference. *Sigh!*

    Any tips?

  7. #7
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,595

    Re: Accessing Controls from Worker Threads

    I've always wondered how you were supposed to do this 'properly' ... now I know Thanks jmc

    EDIT: to the person who posted above me - has that actually got anything at all to do with this thread? You would almost certainly be better off posting in the VB.NET forum.
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  8. #8
    Addicted Member
    Join Date
    Mar 2008
    Posts
    150

    Re: Accessing Controls from Worker Threads

    If you know that an invoke will always be required in accessing your control from a worker thread, suggesting that the function you're calling is only used by your worker thread, is it a 'safe option' to call the delegate directly without going via the 'real' function?

    I'm not sure if that makes sense, so using the following code as an example:

    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3.     Private Function GetTextBoxText() As String
    4.         If Me.TextBox1.InvokeRequired Then
    5.             Return CStr(Me.TextBox1.Invoke(New GetTextBoxTextInvoker(AddressOf GetTextBoxText)))
    6.         Else
    7.             Return Me.TextBox1.Text
    8.         End If
    9.     End Function

    Rather than calling GetTextBoxText() from your worker thread, and it invoking itself via the delegate function GetTextBoxTextInvoker()...can you safely call GetTextBoxTextInvoker() from your worker thread instead and 'cut out the middle man'?

    To me it saves a few lines of code being executed for no apparent reason, and I say 'no apparent reason' because I'm not sure if there's another reason to do so...and this is the basis of my question.

    I'm happy to be wrong, and I've tested this, and it works, but I'm just wondering if there's a 'gotcha' in here somewhere.

  9. #9

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by scootabug
    If you know that an invoke will always be required in accessing your control from a worker thread, suggesting that the function you're calling is only used by your worker thread, is it a 'safe option' to call the delegate directly without going via the 'real' function?

    I'm not sure if that makes sense, so using the following code as an example:

    vb.net Code:
    1. Private Delegate Function GetTextBoxTextInvoker() As String
    2.  
    3.     Private Function GetTextBoxText() As String
    4.         If Me.TextBox1.InvokeRequired Then
    5.             Return CStr(Me.TextBox1.Invoke(New GetTextBoxTextInvoker(AddressOf GetTextBoxText)))
    6.         Else
    7.             Return Me.TextBox1.Text
    8.         End If
    9.     End Function

    Rather than calling GetTextBoxText() from your worker thread, and it invoking itself via the delegate function GetTextBoxTextInvoker()...can you safely call GetTextBoxTextInvoker() from your worker thread instead and 'cut out the middle man'?

    To me it saves a few lines of code being executed for no apparent reason, and I say 'no apparent reason' because I'm not sure if there's another reason to do so...and this is the basis of my question.

    I'm happy to be wrong, and I've tested this, and it works, but I'm just wondering if there's a 'gotcha' in here somewhere.
    Yes, you certainly can do that. For instance, if you wanted to append text to a TextBox then you could forgo the "middleman" and just create a delegate to invoke the AppendText method of the TextBox directly. If you are accessing a property rather than a method, or you're accessing multiple members, then you're still going to need to write a method that you can invoke on the UI thread though. I prefer to stick to the pattern I've demonstrated for clarity and consistency. If you always do this the same way and all the threading-specific code is always in the one method then it's always clear exactly what you're doing.

    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

  10. #10
    Addicted Member
    Join Date
    Mar 2008
    Posts
    150

    Re: Accessing Controls from Worker Threads

    Great, thanks!

  11. #11
    Fanatic Member
    Join Date
    Feb 2006
    Posts
    607

    Re: Accessing Controls from Worker Threads

    scoota, I dont seem to understand what your talking about. Mind to give an example where you cut out the 'middle' man?

    Learning purposes only! Thanks

  12. #12

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by masfenix
    scoota, I dont seem to understand what your talking about. Mind to give an example where you cut out the 'middle' man?

    Learning purposes only! Thanks
    If you want to append text to a TextBox then, by my pattern, you'd do this:
    vb.net Code:
    1. Private Delegate Sub AppendTextToTextBoxInvoker(ByVal text As String)
    2.  
    3. Private Sub AppendTextToTextBox(ByVal text As String)
    4.     If Me.TextBox1.InvokeRequired Then
    5.         Me.TextBox1.Invoke(New AppendTextToTextBoxInvoker(AddressOf AppendTextToTextBox), text)
    6.     Else
    7.         Me.TextBox1.AppendText(text)
    8.     End If
    9. End Sub
    and then somewhere in your background thread you'd do this:
    vb.net Code:
    1. Me.AppendTextToTextBox(someString)
    By cutting out the middle man we mean removing the need for the AppendTextToTextBox method. Instead of that last line calling AppendTextToTextBox and it invoking itself, you can just invoke the TextBox's AppendText method directly:
    vb.net Code:
    1. Me.TextBox1.Invoke(New AppendTextToTextBoxInvoker(AddressOf Me.TextBox1.AppendText), someString)
    I like the pattern I've described because, hopefully, it makes it clear to those who might not understand the process exactly what's happening. As you get more comfortable with multi-threading then it becomes easier to take "shortcuts". As is the case in so many situations, it's best to lay down and learn the rules first, then learn where and when it's OK to break the rules later.

    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

  13. #13
    Fanatic Member
    Join Date
    Feb 2006
    Posts
    607

    Re: Accessing Controls from Worker Threads

    Thankyou! I cant even imagine doing this on something like C/C++. Hehe you'd have to code like 50 lines just to acomplish a single task.

  14. #14
    Junior Member
    Join Date
    Oct 2008
    Posts
    26

    Re: Accessing Controls from Worker Threads

    This doesn't work for LIstboxes or Comboxes because it doesn't pass the selected index. How can I accomplish that?

  15. #15

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by sebex
    This doesn't work for LIstboxes or Comboxes because it doesn't pass the selected index. How can I accomplish that?
    Um, yes it does. The SelectedIndex is just a number and I've shown you how to pass data in both directions using this technique. Post #2 shows you how to pass data from the background thread to the UI thread. Post #3 shows you how to return data from the UI thread to the background thread. Think about it. Post #3 uses a method named GetTextBoxText to get the Text of a TextBox. Does that really sound all that different to getting the SelectedIndex of a ListBox or ComboBox? You use the exact same pattern and just change the detail. A logical name for the method would be GetListControlSelectedIndex. That should make it obvious what needs changing.

    Note that ListControl is the base class of both the ListBox and the ComboBox from which they both inherit the SelectedIndex property, so you can create a single method that will work for both, or you can write specific methods if you prefer.

    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

  16. #16

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

    Re: Accessing Controls from Worker Threads

    Here's the last example from post #1 redone for WPF:
    vb.net Code:
    1. Private WithEvents worker As New BackgroundWorker
    2.  
    3. Private Sub Window1_Loaded(ByVal sender As Object, _
    4.                            ByVal e As RoutedEventArgs) Handles MyBase.Loaded
    5.     Thread.CurrentThread.Name = "UI Thread"
    6. End Sub
    7.  
    8. Private Sub Button1_Click(ByVal sender As Object, _
    9.                           ByVal e As RoutedEventArgs) Handles Button1.Click
    10.     Me.ResetTextBoxText()
    11.  
    12.     Me.worker.RunWorkerAsync()
    13. End Sub
    14.  
    15. Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
    16.                                      ByVal e As DoWorkEventArgs) Handles worker.DoWork
    17.     Thread.CurrentThread.Name = "Worker Thread"
    18.  
    19.     Me.ResetTextBoxText()
    20. End Sub
    21.  
    22. Private Sub ResetTextBoxText()
    23.     Debug.WriteLine(Thread.CurrentThread.Name, "ResetTextBoxText")
    24.  
    25.     If Me.TextBox1.Dispatcher.CheckAccess() Then
    26.         Me.TextBox1.Clear()
    27.     Else
    28.         Me.TextBox1.Dispatcher.Invoke(New Action(AddressOf ResetTextBoxText))
    29.     End If
    30. End Sub
    Last edited by jmcilhinney; May 30th, 2010 at 12:01 AM.

    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

  17. #17

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

    Re: Accessing Controls from Worker Threads

    Here's the last example from post #1 reworked to use a SynchronizationContext:
    vb.net Code:
    1. Private context As Threading.SynchronizationContext = Threading.SynchronizationContext.Current
    2.  
    3. Private Sub Form1_Load(ByVal sender As System.Object, _
    4.                        ByVal e As System.EventArgs) Handles MyBase.Load
    5.     Threading.Thread.CurrentThread.Name = "UI Thread"
    6. End Sub
    7.  
    8. Private Sub Button1_Click(ByVal sender As System.Object, _
    9.                           ByVal e As System.EventArgs) Handles Button1.Click
    10.     Me.context.Send(AddressOf Me.ResetTextBoxText, Nothing)
    11.  
    12.     Me.BackgroundWorker1.RunWorkerAsync()
    13. End Sub
    14.  
    15. Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
    16.                                      ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    17.     Threading.Thread.CurrentThread.Name = "Worker Thread"
    18.  
    19.     Me.context.Send(AddressOf Me.ResetTextBoxText, Nothing)
    20. End Sub
    21.  
    22. Private Sub ResetTextBoxText(ByVal userState As Object)
    23.     Debug.WriteLine(Threading.Thread.CurrentThread.Name, "ResetTextBoxText")
    24.  
    25.     Me.TextBox1.ResetText()
    26. End Sub
    Notice that, in this case, there's no need to check which thread you're on. If in doubt, you simply tell the SynchronizationContext to Send the method call to the appropriate thread, no matter what thread you're currently on. Note that Send is to Invoke as Post is to BeginInvoke, i.e. one is synchronous and one is asynchronous.

    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

  18. #18
    Wait... what? weirddemon's Avatar
    Join Date
    Jan 2009
    Location
    USA
    Posts
    3,827

    Re: Accessing Controls from Worker Threads

    JMC, in regards to post #17, if you use that example as is, it isn't multi-threaded. Not from what I can tell. When it tries to reset the TextBox, I quickly try to press a different blank button and I can't because the UI thread is busy.

    I launched a process and called waitforexit in the ResetTextBoxTest method to be definitive, and the UI was still not responsive.

    Am I missing something?
    CodeBank contributions: Process Manager, Temp File Cleaner

    Quote Originally Posted by SJWhiteley
    "game trainer" is the same as calling the act of robbing a bank "wealth redistribution"....

  19. #19

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by weirddemon View Post
    JMC, in regards to post #17, if you use that example as is, it isn't multi-threaded. Not from what I can tell. When it tries to reset the TextBox, I quickly try to press a different blank button and I can't because the UI thread is busy.

    I launched a process and called waitforexit in the ResetTextBoxTest method to be definitive, and the UI was still not responsive.

    Am I missing something?
    ResetTextBoxText is executed on the UI thread. It must be, because it's accessing a control directly. The whole purpose of this thread is how to deal with the fact that you can't access controls directly on anything but the UI thread.

    In that example, it's the DoWork event handler of the BackgroundWorker that is executed on the background thread. That's the whole point of the BackgroundWorker: you call RunWorkerAsync and the DoWork event is raised on a background thread, where you do the work. It's the Send method of the SynchronizationContext that marshals the method call to the UI thread, instead of calling Invoke.

    The BackgroundWorker also exists so that you don't have to worry about doing things like using a SynchronizationContext or ISynchronizeInvoke, but I just used it for simplicity in this example. Normally, if you need to update the UI when using a BackgroundWorker, you would use the ReportProgress method and ProgressChanged event and/or the RunWorkerCompleted event. I have another CodeBank thread that deals with that subject though.

    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

  20. #20
    Lively Member
    Join Date
    Jan 2007
    Posts
    72

    Re: Accessing Controls from Worker Threads

    Thank You Jim.

    This/These examples are really great and explained nicely.
    But what bothers me that VB doesn't have a delegate to do a lot controls in one go.
    If you have/collect a lot of data from an bgworker, you have to split it all up to do each and every control in single steps.
    Forces you to rethink everything.

    Kind Regards, Starf0x

  21. #21

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by Starf0x View Post
    Thank You Jim.

    This/These examples are really great and explained nicely.
    But what bothers me that VB doesn't have a delegate to do a lot controls in one go.
    If you have/collect a lot of data from an bgworker, you have to split it all up to do each and every control in single steps.
    Forces you to rethink everything.

    Kind Regards, Starf0x
    You are looking at this in completely the wrong way. Delegates have nothing specific to do with controls. A delegate is simply a reference to a method. That's it, that's all. In these examples, I'm using the Invoke method of a control to marshal a delegate onto the UI thread. Once you are on the UI thread, you you can do anything you want. If you want to update a thousand controls then update a thousand controls. The delegate has nothing whatsoever to do with that.
    vb.net Code:
    1. Private Sub EnableEveryControl()
    2.     If Me.InvokeRequired Then
    3.         Me.Invoke(New MethodInvoker(AddressOf EnableEveryControl))
    4.     Else
    5.         For Each ctl In Me.Controls
    6.             ctl.Enabled = True
    7.         Next
    8.     End If
    9. End Sub
    As you can see, this code uses the very same MethodInvoker delegate as I used in previous examples, yet the code is able to affect every control on the form, whether that is one or one million. The delegate has NOTHING to do with the controls. It is merely a means to invoke a method on the UI thread. What you do in that method is COMPLETELY up to you, as it always is.

    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

  22. #22
    Frenzied Member stateofidleness's Avatar
    Join Date
    Jan 2009
    Posts
    1,780

    Re: Accessing Controls from Worker Threads

    Wow JMC, I know i've posted a lot of "threading" questions recently, and this thread actually makes is so much clearer. Thanks for taking the time to write it up with such clarity!

  23. #23
    Fanatic Member
    Join Date
    Oct 2007
    Posts
    544

    Re: Accessing Controls from Worker Threads

    Also because the invoke happens on Main UI thread anyway, we don't need to do synclock right?>

  24. #24

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

    Re: Accessing Controls from Worker Threads

    Quote Originally Posted by teguh123 View Post
    Also because the invoke happens on Main UI thread anyway, we don't need to do synclock right?>
    When using my pattern, whatever you execute inside the Else block is executed on the UI thread. As such, only one instance of that code can be executed at a time. That means that you don't need to synchronise that specific block of code to protect it from itself, but you may still have to protect against undesirable interactions with other blocks of code that may be executed on other threads.

    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

  25. #25
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Accessing Controls from Worker Threads

    Just wanted to mention I made an extension method that you can use to get and set properties from any control via this same method (except you don't have to type the delegate and invocation each time).
    It can be found here:
    http://www.vbforums.com/showthread.php?t=646484

  26. #26
    Junior Member rkinci's Avatar
    Join Date
    Jan 2012
    Location
    Maine
    Posts
    19

    Re: Accessing Controls from Worker Threads

    Very helpful. Thanks.

  27. #27
    Hyperactive Member
    Join Date
    Jan 2007
    Posts
    415

    Re: Accessing Controls from Worker Threads

    This is the best and simplest explanation I have seen for threading. There were a couple of close "thirds" out there..Not quite "Seconds".
    Thanks jmc!

  28. #28
    Registered User
    Join Date
    Jun 2014
    Posts
    1

    Re: Accessing Controls from Worker Threads

    Thank you very much! It Helps me alot!

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.