locked
Passing a control from a thread RRS feed

  • Question

  • Hello

    I have a program which has a rather complicated component within it.  This component has a number of features that all run at once and will cause lag with the main application which isn't acceptable.

    To resolve this issue I'd like to run these processes on the component in another thread.  I've created a simple test version to work out how I'm going to do this, however I've run into a few problems.

    The code I have is below.

    Public Class ucComponentThreadingTest
    
      Public Event RunComplete(ByVal TheUC As ucComponentThreadingTest)
    
      Public Property txt1() As String
        Get
          Return TextBox1.Text
        End Get
        Set(ByVal value As String)
    
          If value.Trim.Length > 0 Then
            TextBox1.Text = value
    
            Dim LoopThread As New System.Threading.Thread(AddressOf RunStuff)
            LoopThread.Start()
    
          End If
        End Set
      End Property
    
      Public Sub RunStuff()
    
        Dim ucted As New ucComponentThreadingTest
        Dim u As ucComponentThreadingTest = CType(Me, ucComponentThreadingTest)
    
        Dim varX As Integer
        Dim varY As Integer
        Dim varTotalAsOfNow As Double
    
        For varX = 1 To 15000
          For varY = 1 To 50000
            varTotalAsOfNow += 1
          Next
        Next
    
        ucted.TextBox2.Text = "Run Complete"
        ucted.TextBox3.Text = "Just passed via control"
    
        RaiseEvent RunComplete(ucted)
    
      End Sub
    
    End Class
    
    Public Class frmComponentThreadingTest
    
      Public Delegate Sub Fhandler(ByVal value As ucComponentThreadingTest)
    
      Sub UcComponentThreadingTest1_RunComplete(ByVal value As ucComponentThreadingTest) Handles UcComponentThreadingTest1.RunComplete
        Me.BeginInvoke(New Fhandler(AddressOf UpdateComponent), New Object() {value})
      End Sub
    
      Sub UpdateComponent(ByVal TheUC As ucComponentThreadingTest)
        'Shows the RunComplete message
    
        'This works, but there will be to many controls to pass back
        UcComponentThreadingTest1.TextBox2.Text = TheUC.TextBox2.Text
    
        'This doesn't work, but would be ideal
        UcComponentThreadingTest1 = TheUC
      End Sub
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        UcComponentThreadingTest1.txt1 = TextBox1.Text
      End Sub
      
    End Class
    

    I can pass back one control at a time, however I'd like to fill a number of controls all at once.

    Can someone please either a) tell me what I'm doing wrong or b) suggest a better way of doing it?

    Thanks for your time

    Jay

     

    Tuesday, December 21, 2010 11:03 AM

Answers

  • If you use a BackgroundWorker (q.v.), you can pass it an object, for example a class instance containing all the strings you want to process. Then when the worker hs completed, you can use its .Result to get data back (e.g. as a class). Also, you don't want to be trying to directly access UI controls from a thread different to the UI thread.  Does that make sense in the context of your question?

    For example, I put three textboxes and a button on a form, with the following code:

    Imports System.ComponentModel
    
    Public Class Form1
    
      Dim mySlowEntity As slowText
    
      Public Class slowText
    
        Private WithEvents backgroundWorker1 As BackgroundWorker
    
        ' hold references to the textboxes we're going to interact with
        Private _txtBox1 As TextBox
        Private _txtBox2 As TextBox
    
        Public Sub New(ByVal txtBox1 As TextBox, ByVal txtBox2 As TextBox)
          _txtBox1 = txtBox1
          _txtBox2 = txtBox2
        End Sub
    
        ' something to use with the backgroundworker
        Private Class inputData
          Property n As Integer
          Property z As Double
        End Class
    
        ' something else to use with the backgroundworker
        Private Class resultSet
          Property string1
          Property string2
        End Class
    
        ' do the actual lengthy processing here
        Private Function doSomething(ByVal myArgs As inputData, ByVal worker As BackgroundWorker, ByVal e As DoWorkEventArgs) As resultSet
          Threading.Thread.Sleep(1000)
          Return New resultSet With {.string1 = Math.Pow(myArgs.z, myArgs.n).ToString, .string2 = DateTime.Now.Ticks.ToString}
        End Function
    
        ' start the work
        Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
          ' Get the BackgroundWorker object that raised this event.
          Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
          ' Assign the result of the computation to the Result property of the DoWorkEventArgs object.
          ' This is will be available to the RunWorkerCompleted eventhandler.
          Dim someData = New inputData With {.n = 2, .z = 1.2}
          e.Result = doSomething(someData, worker, e)
        End Sub
    
        Private Sub backgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles backgroundWorker1.RunWorkerCompleted
          ' First, handle the case where an exception was thrown.
          If (e.Error IsNot Nothing) Then
            MessageBox.Show(e.Error.Message)
            ' do something more useful to handle the error
          End If
    
          Dim theResult As resultSet = DirectCast(e.Result, resultSet)
          _txtBox2.Text = theResult.string1 & " " & theResult.string2
        End Sub
    
    
    
        Private _txt1 As String
    
        Public Property txt1 As String
          Get
            Return _txt1
          End Get
          Set(ByVal value As String)
            If value.Trim.Length > 0 Then
              _txt1 = value
              _txtBox1.Text = value
    
              ' do something that takes a long time, without slowing the UI
              backgroundWorker1 = New BackgroundWorker
              backgroundWorker1.WorkerReportsProgress = False
              backgroundWorker1.WorkerSupportsCancellation = False
              backgroundWorker1.RunWorkerAsync()
    
            End If
          End Set
        End Property
    
      End Class
    
      Private Sub animateTextBox()
        TextBox3.Text = (DateTime.Now.Ticks And &HFFFF).ToString
      End Sub
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        mySlowEntity.txt1 = "Hello"
      End Sub
    
      Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        mySlowEntity = New slowText(TextBox1, TextBox2)
    
        ' do something in the UI to demonstrate it remains responsive
        Dim mt As New Timer
        mt.Interval = 200
        AddHandler mt.Tick, AddressOf animateTextBox
        mt.Start()
      End Sub
    
    End Class
    
    
    The third textbox updates every 200ms. When you click the button, the first textbox is immediately updated, and the second textbox is populated once the processing is done.

    HTH,

    Andrew

    • Proposed as answer by Rudedog2 Friday, December 24, 2010 5:07 PM
    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Thursday, December 23, 2010 2:35 PM
  • There's one behavior that you must keep in mind with WinForms controls and threads.  OS handles to unmanaged resources.

    Before the CLR can display a form or a control, it must obtain a handle from the OS.  It also tracks which thread obtained the handle.  If a thread attempts to reference the object in such a way that affects the resource associated with the handle, and exception will be thrown if the thread does not own the handle.

    In other words, never reference form controls from any thread that did not declare and instantiate the control.  In order to safely reference form controls from a separate thread, you must check the InvokeRequired property.

    Control.InvokeRequired Property (System.Windows.Forms)

    Calling Control.Invoke will cause the thread the owns the handle associated with that instance to make the method call associated with the delegate being passed as a parameter.  99.999999% of the time, this means that the proper UI thread will execute the method.

    Hope this helps.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Thursday, December 23, 2010 2:55 PM
  • How about

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
      Dim b = CType(sender, Button)
      b.BackColor = Color.Red
    End Sub
    
    

    You can use control.SuspendLayout to pause the control's layout logic and .ResumeLayout to resume it in cases where you're making many changes to a control.

    HTH,

    Andrew

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 1:08 PM
  • @ Jamie Brown:

    http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/faa5da25-f50c-4a87-b1e8-ed7a6ec8cd71

    I made some recent posts about threading and the APM.  That thread includes an explanation of InvokeRequired, but the comments build on previous replies.  Here are the comments about InvokeRequired from that thread. 

    I describe the execution of the method you remarked about.

    This part is not part of the APM. 

     

            delegate void RichTextBoxHandler(string s);

     

            private void setRichTextBoxText(string s)

            {

                if ( richDisplay.InvokeRequired )

                {

                    RichTextBoxHandler r = new RichTextBoxHandler(setRichTextBoxText);

                    Invoke(r, s);

                }

                else

                {

                    richDisplay.Text = s;

                }

            }

     

    That shows how to update a form control from another thread.  You cannot access or reference form controls from another thread.  You are allowed to reference control only on the thread that declared them.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx


    What on earth is that piece of code doing?  InvokeRequired?

    Form controls are easy to use but one rule must be followed.  Controls inherit from MarshalByRefObject, which is a wrapper for unmanaged resources.  The CLR prevents managed code from accessing unmanaged resources on a different thread than the one that defined the instance object that accesses the unmanaged resource.  This means that Form controls must only be referenced by the main UI thread.

    If you use a separate thread to perform some background processing, you cannot update your form from the background thread.  You must pass the information from one thread to another.  Prior to .NET this was a complicated and delicate task.  The Framework designers have provided you with a simple mechanism to perform this operation by using delegates.  Invoke.

    ( Recall what a wrote above about delegates.  Calling delegate.Invoke causes the method to be executed on the same thread.  Calling delegate.BeginInvoke causes the method to be executed on a separate thread.  Actually, it is more subtle than that.  When an object is instantiated the CLR knows which thread did it. )

    ( Objects that consume unmanaged resources---controls consume graphics memory---are assigned a 'handle' to the unmanaged resoure.  The CLR tracks how these handles are assigned to threads.  The Windows OS furnishes handles for applications to use.  Calling Invoke actually causes the CLR to call the thread that owns the handle to the unmanaged resource. )

    ( The net result is that calling Invoke on an object instance causes the method to execute on the thread that defined the object instance! )

    What does InvokeRequired mean?  What is it checking?  Every thread has an ID.  Delegates provide us with a mechanism to assign/delegate execution of a method to a separate thread.  The property InvokeRequired returns a boolean value, true or false.  Every control keeps track of which thread defined and instantiated the instance.  InvokeRequired simply compares thread IDs.

    The property name InvokeRequired could have been given this long name, TheCallingThreadIsNOTTheThreadThatDeclaredThisControl.  I am glad that they shortened it, but the chosen name can seem rather ambiguous.  The name tells you what must be done.  You must invoke a delegate to switch execution to the proper thread that defined the control.

    This switching is performed in a manner very similar to the APM mechanism.  You call Invoke on the control, and pass it a target method.  In this case, we are passing a delegate that points to the same method!  We also pass the same method parameter through the delegate!  This causes the method to be executed a 2nd time.  On this 2nd pass, InvokeRequired evaluates to false because the proper thread is executing the method. 

    Now the control can be safely updated.  When execution completes on this second pass, execution returns to the line where the Invoke was called.  This means execution is back on the separate thread that initially called the method.  When the method completes, execution returns to the caller.  This means that the separate thread continues about its' business totally oblivous to the fact that its' actual update was performed on a separate thread.

    I told you threads were dumb.

    Hope this helps.

    Rudy   =8^D



    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Edited by Rudedog2 Friday, December 24, 2010 2:27 PM
    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 2:00 PM
  • The line of code "Invoke" is calling Invoke on the form instance, which obtained its' handle from the same thread as all of its' child controls.  Actually, the child controls obtain their handles from the same thread as the parent control, or container.

    The link from the previous post shows a similar method written in VB using the base class, Control.  The MSDN library typically shows documentation in the base class that defines the class member.  That link applies to any type that inherits Control, like Form class and most WinForms controls.

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 2:17 PM
  • Sorry guys but I think I'm really ____ at writing examples and I over-simpified it.  It was mean't more of a copying a control over another... and setting all of the properties to the values in the new instance.

    I was trying to simplify this bit of the original post.

     Sub UpdateComponent(ByVal TheUC As ucComponentThreadingTest)
    
     'Shows the RunComplete message
    
    
    
     'This works, but there will be to many controls to pass back
    
     UcComponentThreadingTest1.TextBox2.Text = TheUC.TextBox2.Text
    
    
    
     'This doesn't work, but would be ideal
    
     UcComponentThreadingTest1 = TheUC
    
     End Sub
    
    
    
    

    I could write something that will loop through all of the child controls of TheUC and copy all of the properties into UcComponentThreadingTest1.  This just seems like a really bad way of doing things, and I would expect there to be something like  

    TheUC.CopyTo(UcComponentThreadingTest1)
    
    'Or
    
    UcComponentThreadingTest1 = TheUC.Clone()
    
    

     Sorry I'm not very good at explaining myself.

     


    My post about "InvokeRequired" explains why your approach does not work.  I guess we cross-posted.  I just now saw your reply.

    You create a form control on a thread, which means it is assigned a handle associated with that thread.  Then you appear to try to use that control on a form created on another thread.  A form with a handle assigned by a different thread.  That is not permitted.

    Besides, even if it were permitted consider what would occur.

    Dim originalControl as CustomControl 'creates object instance that is added to Form.Controls.
    

    Now suppose that you could re-assign the variable, originalControl, to your new instance created on the thread.

    Me.originalControl = controlCreatedByThread
    

    The original control instance was part of the Form.Controls.  The instance was referenced by the variable, originalControl.  You have re-assigned the variable to reference another instance.  The new instance is not part of Form.Controls.  The old instance is now unreferenced by the variable, but is still referenced by the Form.Controls collection. 

    See the picture that is forming?  You simply re-assigned the local variable, but made no changes to the Controls collection.  In fact, you have created an orpahn object in the Controls collectioin that cannot be disposed until the form itself is disposed. 

    If you specifically remove that object instance from the collection, then it can be disposed.  But, the new instance is still not part of the collection until you specifically add it. (which will throw a cross thread exception because it has a handle created on a different thread)

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Friday, December 24, 2010 4:57 PM
  • Please use the above explanation of InvokeRequired to understand how this sample works.  Crfeate a blank form with a single button.

    Imports System.Threading
    
    Public Class Form2
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim starter As ThreadStart = New ThreadStart(AddressOf ThreadMethod)
        Dim thread As Thread = New Thread(starter)
        thread.Start()
      End Sub
    
      Delegate Sub UpdateTitleTextDelegate(ByVal text As String)
    
      Private Sub UpdateTitleText(ByVal text As String)
        If Me.InvokeRequired Then
          Dim method As New UpdateTitleTextDelegate(AddressOf Me.UpdateTitleText)
          Dim methodParameters As Object() = {text}
          Me.Invoke(method, methodParameters)
        Else
          Me.Text = text
        End If
      End Sub
    
      Private Sub ThreadMethod()
        Dim index As Integer
    
        For index = 1 To 10
          Dim text As String = String.Empty
          UpdateTitleText("The index is " & index.ToString())
          Thread.Sleep(500)
        Next
    
      End Sub
    End Class
    

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Friday, December 24, 2010 7:24 PM

All replies

  • Hi Jamie,

    Thanks for your post.

    I am doing research on this issue. If I get any better suggestions, I will post it here. Thanks for your understanding.

    Best regards


    Liliane Teng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, December 23, 2010 9:36 AM
  • Hello again,

    I need some clarifications to ensure we have the right direction.

    1. What do you mean

    "I can pass back one control at a time, however I'd like to fill a number of controls all at once"?

    2. Does the property defined in Class ucComponentThreadingTest have any relations with passing controls?

    Based on my understanding, when you passing the values of the controls, you don't want to write code for each Textbox, you want to implement at one time. Is it so?

        'This doesn't work, but would be ideal

        UcComponentThreadingTest1 = TheUC

    For this code, because you are passing a reference, so you only pass a address but not all the property values. If you want to pass all the TextBoxs'Text property, I suggest you could write a deep copy function in Class ucComponentThreadingTest and load this method in Sub UpdateComponent.

    If I misunderstood, please feel free to follow up.

    Best regards


    Liliane Teng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, December 23, 2010 11:00 AM
  • You better can make from complicated component a good component. 

    Complicated is mostly a synonym for not well done and shows mostly a lack of knowledge.

    Creating more spaghetti around that means mostly even more trouble.

     


    Merry Christmas
    Cor
    Thursday, December 23, 2010 12:54 PM
  • If you use a BackgroundWorker (q.v.), you can pass it an object, for example a class instance containing all the strings you want to process. Then when the worker hs completed, you can use its .Result to get data back (e.g. as a class). Also, you don't want to be trying to directly access UI controls from a thread different to the UI thread.  Does that make sense in the context of your question?

    For example, I put three textboxes and a button on a form, with the following code:

    Imports System.ComponentModel
    
    Public Class Form1
    
      Dim mySlowEntity As slowText
    
      Public Class slowText
    
        Private WithEvents backgroundWorker1 As BackgroundWorker
    
        ' hold references to the textboxes we're going to interact with
        Private _txtBox1 As TextBox
        Private _txtBox2 As TextBox
    
        Public Sub New(ByVal txtBox1 As TextBox, ByVal txtBox2 As TextBox)
          _txtBox1 = txtBox1
          _txtBox2 = txtBox2
        End Sub
    
        ' something to use with the backgroundworker
        Private Class inputData
          Property n As Integer
          Property z As Double
        End Class
    
        ' something else to use with the backgroundworker
        Private Class resultSet
          Property string1
          Property string2
        End Class
    
        ' do the actual lengthy processing here
        Private Function doSomething(ByVal myArgs As inputData, ByVal worker As BackgroundWorker, ByVal e As DoWorkEventArgs) As resultSet
          Threading.Thread.Sleep(1000)
          Return New resultSet With {.string1 = Math.Pow(myArgs.z, myArgs.n).ToString, .string2 = DateTime.Now.Ticks.ToString}
        End Function
    
        ' start the work
        Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles backgroundWorker1.DoWork
          ' Get the BackgroundWorker object that raised this event.
          Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
          ' Assign the result of the computation to the Result property of the DoWorkEventArgs object.
          ' This is will be available to the RunWorkerCompleted eventhandler.
          Dim someData = New inputData With {.n = 2, .z = 1.2}
          e.Result = doSomething(someData, worker, e)
        End Sub
    
        Private Sub backgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles backgroundWorker1.RunWorkerCompleted
          ' First, handle the case where an exception was thrown.
          If (e.Error IsNot Nothing) Then
            MessageBox.Show(e.Error.Message)
            ' do something more useful to handle the error
          End If
    
          Dim theResult As resultSet = DirectCast(e.Result, resultSet)
          _txtBox2.Text = theResult.string1 & " " & theResult.string2
        End Sub
    
    
    
        Private _txt1 As String
    
        Public Property txt1 As String
          Get
            Return _txt1
          End Get
          Set(ByVal value As String)
            If value.Trim.Length > 0 Then
              _txt1 = value
              _txtBox1.Text = value
    
              ' do something that takes a long time, without slowing the UI
              backgroundWorker1 = New BackgroundWorker
              backgroundWorker1.WorkerReportsProgress = False
              backgroundWorker1.WorkerSupportsCancellation = False
              backgroundWorker1.RunWorkerAsync()
    
            End If
          End Set
        End Property
    
      End Class
    
      Private Sub animateTextBox()
        TextBox3.Text = (DateTime.Now.Ticks And &HFFFF).ToString
      End Sub
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        mySlowEntity.txt1 = "Hello"
      End Sub
    
      Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        mySlowEntity = New slowText(TextBox1, TextBox2)
    
        ' do something in the UI to demonstrate it remains responsive
        Dim mt As New Timer
        mt.Interval = 200
        AddHandler mt.Tick, AddressOf animateTextBox
        mt.Start()
      End Sub
    
    End Class
    
    
    The third textbox updates every 200ms. When you click the button, the first textbox is immediately updated, and the second textbox is populated once the processing is done.

    HTH,

    Andrew

    • Proposed as answer by Rudedog2 Friday, December 24, 2010 5:07 PM
    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Thursday, December 23, 2010 2:35 PM
  • P.S. I meant to move the SlowText class outside the Form1 class.

    --
    Andrew

    Thursday, December 23, 2010 2:43 PM
  • There's one behavior that you must keep in mind with WinForms controls and threads.  OS handles to unmanaged resources.

    Before the CLR can display a form or a control, it must obtain a handle from the OS.  It also tracks which thread obtained the handle.  If a thread attempts to reference the object in such a way that affects the resource associated with the handle, and exception will be thrown if the thread does not own the handle.

    In other words, never reference form controls from any thread that did not declare and instantiate the control.  In order to safely reference form controls from a separate thread, you must check the InvokeRequired property.

    Control.InvokeRequired Property (System.Windows.Forms)

    Calling Control.Invoke will cause the thread the owns the handle associated with that instance to make the method call associated with the delegate being passed as a parameter.  99.999999% of the time, this means that the proper UI thread will execute the method.

    Hope this helps.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Thursday, December 23, 2010 2:55 PM
  • Thanks for the responses.

    The code I posted in the original thread was a much simplified version of my actual component.  This component sits on a form and is displayed at all times, occasionally the form will call a public method on the component which update some data shown on the form.  The component itself is simple to use and fairly efficient, however it does a lot and therefore can take a second for this public method to run.

    Every way I've tried running the public method in a separate thread it has required creating another instance of the component and then try setting the controls of actual component to that of the 'new' instance.

    I've tried Andrew's excellent example of the background worker, along with the original multi-threaded approach, but both fall down at the same hurdle.

      Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim b As New Button
        b.BackColor = Color.Red
        Button2 = b
      End Sub
    

    Really I suppose the code sample is probably the simplest example of where I'm falling over.  Ideally it would turn Button2 red, however it fails.  I could write button2.backcolor = b.backcolour, which is easily done, but then if I were to do this with every property it would take all day, and I'm guessing be rather slow too.

    RudeGod, thanks for the link - I read it, but doesn't that just return whether I need to Invoke the object or not?  Do I require that with my example as I am invoking the object.

    Thanks again for all your help, sorry about the noobish questions.

     

    Friday, December 24, 2010 12:50 PM
  • How about

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
      Dim b = CType(sender, Button)
      b.BackColor = Color.Red
    End Sub
    
    

    You can use control.SuspendLayout to pause the control's layout logic and .ResumeLayout to resume it in cases where you're making many changes to a control.

    HTH,

    Andrew

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 1:08 PM
  • If it is about simple than 

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
     DirectCast(sender, Button).BackColor = Color.Red
    End Sub

    :-)


    Merry Christmas
    Cor
    Friday, December 24, 2010 1:36 PM
  • I had the impression the OP wanted to change several properties ;)

    --
    Andrew

    Friday, December 24, 2010 1:42 PM
  • @ Jamie Brown:

    http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/faa5da25-f50c-4a87-b1e8-ed7a6ec8cd71

    I made some recent posts about threading and the APM.  That thread includes an explanation of InvokeRequired, but the comments build on previous replies.  Here are the comments about InvokeRequired from that thread. 

    I describe the execution of the method you remarked about.

    This part is not part of the APM. 

     

            delegate void RichTextBoxHandler(string s);

     

            private void setRichTextBoxText(string s)

            {

                if ( richDisplay.InvokeRequired )

                {

                    RichTextBoxHandler r = new RichTextBoxHandler(setRichTextBoxText);

                    Invoke(r, s);

                }

                else

                {

                    richDisplay.Text = s;

                }

            }

     

    That shows how to update a form control from another thread.  You cannot access or reference form controls from another thread.  You are allowed to reference control only on the thread that declared them.


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx


    What on earth is that piece of code doing?  InvokeRequired?

    Form controls are easy to use but one rule must be followed.  Controls inherit from MarshalByRefObject, which is a wrapper for unmanaged resources.  The CLR prevents managed code from accessing unmanaged resources on a different thread than the one that defined the instance object that accesses the unmanaged resource.  This means that Form controls must only be referenced by the main UI thread.

    If you use a separate thread to perform some background processing, you cannot update your form from the background thread.  You must pass the information from one thread to another.  Prior to .NET this was a complicated and delicate task.  The Framework designers have provided you with a simple mechanism to perform this operation by using delegates.  Invoke.

    ( Recall what a wrote above about delegates.  Calling delegate.Invoke causes the method to be executed on the same thread.  Calling delegate.BeginInvoke causes the method to be executed on a separate thread.  Actually, it is more subtle than that.  When an object is instantiated the CLR knows which thread did it. )

    ( Objects that consume unmanaged resources---controls consume graphics memory---are assigned a 'handle' to the unmanaged resoure.  The CLR tracks how these handles are assigned to threads.  The Windows OS furnishes handles for applications to use.  Calling Invoke actually causes the CLR to call the thread that owns the handle to the unmanaged resource. )

    ( The net result is that calling Invoke on an object instance causes the method to execute on the thread that defined the object instance! )

    What does InvokeRequired mean?  What is it checking?  Every thread has an ID.  Delegates provide us with a mechanism to assign/delegate execution of a method to a separate thread.  The property InvokeRequired returns a boolean value, true or false.  Every control keeps track of which thread defined and instantiated the instance.  InvokeRequired simply compares thread IDs.

    The property name InvokeRequired could have been given this long name, TheCallingThreadIsNOTTheThreadThatDeclaredThisControl.  I am glad that they shortened it, but the chosen name can seem rather ambiguous.  The name tells you what must be done.  You must invoke a delegate to switch execution to the proper thread that defined the control.

    This switching is performed in a manner very similar to the APM mechanism.  You call Invoke on the control, and pass it a target method.  In this case, we are passing a delegate that points to the same method!  We also pass the same method parameter through the delegate!  This causes the method to be executed a 2nd time.  On this 2nd pass, InvokeRequired evaluates to false because the proper thread is executing the method. 

    Now the control can be safely updated.  When execution completes on this second pass, execution returns to the line where the Invoke was called.  This means execution is back on the separate thread that initially called the method.  When the method completes, execution returns to the caller.  This means that the separate thread continues about its' business totally oblivous to the fact that its' actual update was performed on a separate thread.

    I told you threads were dumb.

    Hope this helps.

    Rudy   =8^D



    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Edited by Rudedog2 Friday, December 24, 2010 2:27 PM
    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 2:00 PM
  • Sorry guys but I think I'm really ____ at writing examples and I over-simpified it.  It was mean't more of a copying a control over another... and setting all of the properties to the values in the new instance.

    I was trying to simplify this bit of the original post.

     Sub UpdateComponent(ByVal TheUC As ucComponentThreadingTest)
      'Shows the RunComplete message
    
      'This works, but there will be to many controls to pass back
      UcComponentThreadingTest1.TextBox2.Text = TheUC.TextBox2.Text
    
      'This doesn't work, but would be ideal
      UcComponentThreadingTest1 = TheUC
     End Sub
    
    

    I could write something that will loop through all of the child controls of TheUC and copy all of the properties into UcComponentThreadingTest1.  This just seems like a really bad way of doing things, and I would expect there to be something like  

    TheUC.CopyTo(UcComponentThreadingTest1)
    'Or
    UcComponentThreadingTest1 = TheUC.Clone()
    

     Sorry I'm not very good at explaining myself.

     

    Friday, December 24, 2010 2:17 PM
  • The line of code "Invoke" is calling Invoke on the form instance, which obtained its' handle from the same thread as all of its' child controls.  Actually, the child controls obtain their handles from the same thread as the parent control, or container.

    The link from the previous post shows a similar method written in VB using the base class, Control.  The MSDN library typically shows documentation in the base class that defines the class member.  That link applies to any type that inherits Control, like Form class and most WinForms controls.

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:34 AM
    Friday, December 24, 2010 2:17 PM
  • Sorry guys but I think I'm really ____ at writing examples and I over-simpified it.  It was mean't more of a copying a control over another... and setting all of the properties to the values in the new instance.

    I was trying to simplify this bit of the original post.

     Sub UpdateComponent(ByVal TheUC As ucComponentThreadingTest)
    
     'Shows the RunComplete message
    
    
    
     'This works, but there will be to many controls to pass back
    
     UcComponentThreadingTest1.TextBox2.Text = TheUC.TextBox2.Text
    
    
    
     'This doesn't work, but would be ideal
    
     UcComponentThreadingTest1 = TheUC
    
     End Sub
    
    
    
    

    I could write something that will loop through all of the child controls of TheUC and copy all of the properties into UcComponentThreadingTest1.  This just seems like a really bad way of doing things, and I would expect there to be something like  

    TheUC.CopyTo(UcComponentThreadingTest1)
    
    'Or
    
    UcComponentThreadingTest1 = TheUC.Clone()
    
    

     Sorry I'm not very good at explaining myself.

     


    My post about "InvokeRequired" explains why your approach does not work.  I guess we cross-posted.  I just now saw your reply.

    You create a form control on a thread, which means it is assigned a handle associated with that thread.  Then you appear to try to use that control on a form created on another thread.  A form with a handle assigned by a different thread.  That is not permitted.

    Besides, even if it were permitted consider what would occur.

    Dim originalControl as CustomControl 'creates object instance that is added to Form.Controls.
    

    Now suppose that you could re-assign the variable, originalControl, to your new instance created on the thread.

    Me.originalControl = controlCreatedByThread
    

    The original control instance was part of the Form.Controls.  The instance was referenced by the variable, originalControl.  You have re-assigned the variable to reference another instance.  The new instance is not part of Form.Controls.  The old instance is now unreferenced by the variable, but is still referenced by the Form.Controls collection. 

    See the picture that is forming?  You simply re-assigned the local variable, but made no changes to the Controls collection.  In fact, you have created an orpahn object in the Controls collectioin that cannot be disposed until the form itself is disposed. 

    If you specifically remove that object instance from the collection, then it can be disposed.  But, the new instance is still not part of the collection until you specifically add it. (which will throw a cross thread exception because it has a handle created on a different thread)

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Friday, December 24, 2010 4:57 PM
  • Please use the above explanation of InvokeRequired to understand how this sample works.  Crfeate a blank form with a single button.

    Imports System.Threading
    
    Public Class Form2
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim starter As ThreadStart = New ThreadStart(AddressOf ThreadMethod)
        Dim thread As Thread = New Thread(starter)
        thread.Start()
      End Sub
    
      Delegate Sub UpdateTitleTextDelegate(ByVal text As String)
    
      Private Sub UpdateTitleText(ByVal text As String)
        If Me.InvokeRequired Then
          Dim method As New UpdateTitleTextDelegate(AddressOf Me.UpdateTitleText)
          Dim methodParameters As Object() = {text}
          Me.Invoke(method, methodParameters)
        Else
          Me.Text = text
        End If
      End Sub
    
      Private Sub ThreadMethod()
        Dim index As Integer
    
        For index = 1 To 10
          Dim text As String = String.Empty
          UpdateTitleText("The index is " & index.ToString())
          Thread.Sleep(500)
        Next
    
      End Sub
    End Class
    

    Hope this helps.

    Rudy   =8^D


    Mark the best replies as answers. "Fooling computers since 1971."

    http://rudedog2.spaces.live.com/default.aspx

    • Marked as answer by Liliane Teng Wednesday, December 29, 2010 10:33 AM
    Friday, December 24, 2010 7:24 PM
  • Hi Jamie,

    I am writing to check the status of the issue on your side. Would you mind letting us know the result of the suggestions? 

    If you need further assistance, please feel free to follow up.

    Have a nice day!

    Best regards


    Liliane Teng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, December 27, 2010 8:58 AM
  • Hi RudeDog

    Thanks for the replies and I really do apprecriate the effort you've put in.  Sorry about the late reply I've been on vacation for two weeks, I spent most of yesterday working through your examples, posts and reading up on threading in general.  But to be honest I still haven't got any further than I did originally. 

    Looking back at my OP, I think I must of been on holiday mode and uploaded the wrong test example.  I rerun the example in the OP and the threading doesn't work at all.  I did actually get the threading working via several methods, I was just having problems updating multiple controls within the control.  Although I suppose I could have completely the wrong approach.

     

    I think rather than come up with more simplified inaccurate examples I'll just explain what I'm trying to do.  The software I'm writing will be used in our call centre, the form is split into two halves.  The left most half has a number of controls on the form itself, such as caller account number, caller name, part number, quantity, etc - these will be displayed at all times.  On the right there is a tab control, one tabpage shows account details, another shows information about a part number.  On the part number tab there is just a user control which contains lots of information about the selected part number.   This user control contains images of the part, dimensions, quantities in stock, delivery dates of new stock, warranty history and a number of other information.  As you can imagine this can take 2-3 seconds to retrieve all the information, during this time the application becomes unresponsive so I intended to use multi-threading to overcome the problem.  This needs to remain a user control as it will be used in other applications in the near future.  Ideally I'd like to wrap up as much of the multi-threading coding in the user control as possible. 

    When I originally made the post I was trying to create another user control just in memory, run the tasks and then copy this in-memory user control over the one on the form, but I could only copy one the contents of one control at a time - which wouldn't be viable.  The most time-consuming parts of the user control are the contents of some of the datagridviews, I'm now wondering whether I should retrieve the simple information in the same thread and then just launch a separate thread for each datagridview.

    Thanks again.

     

    Wednesday, January 5, 2011 10:04 AM
  • Jamie,

    In my ideas you are not busy with creating a well performing program.

    You are trying to use multithreading, despite the fact that the time it needs for your users to handle methods takes longer.

    Almost everybody who has answered has written this between the lines but you are blind for it, therefore I write it here explicitly.

    The time consumed by a any program which has an UI interface is not because of the movements of memory data inside the main memory, but because of the time needed to get that data from an external device of painting it on screen. The latter is done by the display adapter, not by the main processor, the only thing you are adding is a weird process to create a kind of communication between those. This most probably only lengthen the time to get the actual results on screen.

    Yes it is possible, the same as it is possible to travel from Brooklyn to Newark by passing non stop London. So you get answers for that, if you want to do that, feel free, but be aware that you are in fact creating for some the idea that it is advisable to travel from Brooklyn to Newark with a non stop trip over London.

     


    Success
    Cor
    Wednesday, January 5, 2011 10:44 AM
  • Cor,

    I really do apprecriate it when people are trying to help me and you were most useful on a previous question I posted, but to be honest I can't say I would entirely agree with your assessment here.  Maybe I'm missing a trick, or maybe my explanations aren't that clear.

    The user control contains lots of reference information that the user will need, this can take 2 seconds to retrieve all the information from the database as it fires off about 8-9 SQL queries.  The data may not be required for a couple of seconds but the user has a couple of seconds of other inputting to do before.  So currently they type in the part number, it takes two seconds to retrieve the information whilst the user waits, then the user has to spend 2 seconds performing other actions.  So the process takes 4 seconds.  If this could happen side by side the whole process would only take 2 seconds and the user wouldn't be waiting at all.

    I can't see how performing a series of SQL queries in another thread would slow the whole thing down.  Even if it will do, I would still like to try to implement it for my own understand - then I can always remove the code again.  But if I don't try I won't know and I won't learn.

    Thanks

    Jamie

    Thursday, January 6, 2011 9:03 AM
  • This you never told, everybody would have told to do the typing in the foreground and retrieving the data in the background thread 

    But you have only fixed this thread that somehow typing must be done in the background and retrieving in the foreground.

    The in the background retrieved data can be put in an instance, which when if the data is complete can be used in the foreground.

    A simple way of using multi threading for that you need no control in the background thread.

     

     


    Success
    Cor
    Thursday, January 6, 2011 9:22 AM
  • Thats what I'm trying to achieve.  I tried to simplify my examples and code as much as possible and I think that was the problem.

    Currently when focus leaves the part number textbox, it runs the code below.

      Private Sub txtPartNo_LostFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtPartNo.LostFocus
        tcDetails.Visible = True
        tcDetails.SelectedTab = tpPartDetails
        'UcPartInformation1 sits within tpPartDetails
    
        'Check if this is a valid part and notify the user if it isn't. If it isn't don't set the part number in the component
        If UcPartInformation1.PartNo <> txtPartNo.Text.Trim.ToUpper AndAlso txtPartNo.Text.Trim.Length > 0 Then
    
          'The property PartNo calls a method (called FindPartDetails) which runs a series of SQL queries that writes the data to a number of controls on the form
          UcPartInformation1.PartNo = txtPartNo.Text.Trim.ToUpper
    
        End If
    
      End Sub
    

    "The in the background retrieved data can be put in an instance, which when if the data is complete can be used in the foreground.

    A simple way of using multi threading for that you need no control in the background thread."

    I was trying to create an instance of the user control then copy it over the actual user control.  I could get it working on a control by control basis, but I couldn't get it working for the whole control at once.  Which would of been preferrable due to its size.

     

    Thanks again.

    Thursday, January 6, 2011 10:38 AM
  • Hello, Don't know if you still need assistance with this or not, but I've done something similar in the past.  Here's how I overcame

    build a structure or class of all the values you need to set/change - needs to be app-wide so in a module or such

    Class MyCustomProperies
        Public Property1 As String
        Public Property2 As Integer
        'etc.
    End Class
    

     

    then in your UC have a routine that accepts an instance of this class and uses the invoke as mentioned above

    Private Delegate Sub UpdatePropertiesDEL(newValues as MyCustomProperties)
    
    Public UpdateProperties(newValues as MyCustomProperties)
        If Me.InvokeRequired Then
            Me.Invoke(New UpdatePropertiesDEL(AddressOf UpdateProperties), newValues)
        Else
            Me.Property1 = newValues.Property1
            Me.Property2 = newValues.Property2
            'etc.
        End If
    
    End Sub
    


    then just create every thing as normal and from the bg thread call

    Dim theValues As New MyCustomProperties
    theValues.Property1 = "Toast"
    theValues.Property2 = 2
    'etc.
    UC.UpdateProperties(theValues)

     

    really doesn't take as long as it sounds like it would

     

     


    the real JeZteR My New Blog: http://jessievbnettips.blogspot.com/
    • Edited by JeZteRicp Friday, October 7, 2011 5:26 PM
    Friday, October 7, 2011 4:35 PM