VB .NET cross-threading challenge

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • thanksinadvance
    New Member
    • Oct 2007
    • 5

    VB .NET cross-threading challenge

    I'm using a 3rd party .NET library which monitors a peripheral with a backgroundworke r. I've added a handler to catch a raised event, but when I try to update the UI I receive an exception because my UI call is on the raised event thread from the background worker and not the UI thread. The 3rd party library states that UI events should be called with ControlBeginInv oke, but I've had no luck trying to get it to work. The program is supposed to be very simply; so, I'd like to avoid a lot of plumbing if possible. Here's what I have so far. I'm not including all of my attempts to call the UI thread.
    [code=vbnet]
    Public Class frmMain
    Dim m_ctmPeripheral Manager As PeripheralDevic eManager = PeripheralDevic eManager.Single ton

    Private Sub fmrMain_Load(By Val sender As System.Object, ByVal e As System.EventArg s) Handles MyBase.Load
    ' Start the peripheral Manager
    m_ctmPeripheral Manager.Startup (True)
    ' Attach the event handlers
    AddHandler m_ctmPeripheral Manager.Periphe ralActivatedEve nt, AddressOf testEvent
    AddHandler m_ctmPeripheral Manager.Periphe ralDeActivatedE vent, AddressOf testEvent2
    End Sub


    Private Sub testEvent(ByVal sender As Object, ByVal args As PeripheralDevic eEventArgs)
    ' Update the UI TextBox == This fails b/c the UI thread doesn't call this function
    txtOutput.Text = txtOutput.Text & vbCrLf & ("Got " & args.Peripheral Device.Internal Name & ", on slot " & args.Slot.ToStr ing)
    End Sub

    Private Sub testEvent2(ByVa l sender As Object, ByVal args As PeripheralDevic eEventArgs)
    ' Update the UI TextBox == This fails b/c the UI thread doesn't call this function
    txtOutput.Text = txtOutput.Text & vbCrLf & ("Got " & args.Peripheral Device.Internal Name & ", on slot " & args.Slot.ToStr ing)
    End Sub

    End Class[/code]

    TIA.
    Last edited by Frinavale; Oct 30 '07, 06:37 PM. Reason: Added [code] tags to make more legible
  • Shashi Sadasivan
    Recognized Expert Top Contributor
    • Aug 2007
    • 1435

    #2
    I had a similar issue sometime ago.
    and my non main thread was chaging the UI

    I used the control.InvokeR equired for this
    I basically chnaged the UI is this was true.

    It worked for me. But for some reason i think i took the threading off my application.

    good luck

    Comment

    • thanksinadvance
      New Member
      • Oct 2007
      • 5

      #3
      Originally posted by Shashi Sadasivan
      I had a similar issue sometime ago.
      and my non main thread was chaging the UI

      I used the control.InvokeR equired for this
      I basically chnaged the UI is this was true.

      It worked for me. But for some reason i think i took the threading off my application.

      good luck
      Hmmm... InvokeRequired confirms that an Invoke is required. So, in my initial post I stated that I need to call Invoke or BeginInvoke, but I can't seem to get it to work properly. The code initially posted is mine, and it calls the 3rd party .NET library. This library has a (static) singleton which, when set to true, initializes a backgroundworke r thread in the 3rd party library. I have no direct control over the "Black Box," and so I really have no clear alternative other than to figure out how to get the cross-thread function call to work in response to an event raised from the "Black Box." I can't remove threading from the app.

      Comment

      • Shashi Sadasivan
        Recognized Expert Top Contributor
        • Aug 2007
        • 1435

        #4
        If your 3rd party code is controlling the properties of the controls (UI based) and you cannot to anything to that, then i think you should ask them for support for this issue!

        Comment

        • thanksinadvance
          New Member
          • Oct 2007
          • 5

          #5
          The 3rd party library just raises events and provides interfaces to the peripherals. I write the UI. The problem is that when the 3rd party library raises an event (like a device being activated) by using its background worker, I wind up handling that event by calling testEvent (or testEvent2). These are sub's that I wrote, but which are executing under the 3rd party library's background worker thread (which raised the event). Therefore, when I try to manipulate the UI, I get a cross-thread error. I've not been successful in trying to use Invoke or BeginInvoke to make an asynchronous call back to the UI thread (i.e., the code I've written). The help I need is in getting the cross-thread sub call to work.

          Comment

          • Shashi Sadasivan
            Recognized Expert Top Contributor
            • Aug 2007
            • 1435

            #6
            Allright,
            that then brings it back to my first post.
            Check if the control needs to be invoked.
            if the controls invoke required is true then the control was instantiated on a different thread.
            So only on a false should you be changing its properties.
            check the msdn library

            Comment

            • thanksinadvance
              New Member
              • Oct 2007
              • 5

              #7
              Originally posted by Shashi Sadasivan
              Allright,
              that then brings it back to my first post.
              Check if the control needs to be invoked.
              if the controls invoke required is true then the control was instantiated on a different thread.
              So only on a false should you be changing its properties.
              check the msdn library
              The txtOutput.Invok eRequire returns true as the code is currently written. That is the problem I'm trying to overcome. The txtOutput object (a textbox in the UI) was created by me on a form (let's say it is running on thread 10). I need to update its Text property with the date returned from the background worker (3rd party lib) which is operating on a separate thread (let's say it is running on thread 11). So, how do I get to a situation where the invokerequired is false? That is, how do I call from the background worker thread (#11) a change on an object in my UI thread( #10)? I know that I need to use invoke or begininvoke, and I must have tried at least 10 or 15 different says to use invoke or begininvoke, and none that I've tried successfully changes the txtOutput.Text property because I can't break out of the background worker thread to update the UI. Even when I've used Invoke, it still seems to run against the background worker thread, and so, I continually get a runtime error about cross-thread functions calls. Do I call invoke on the txtOutput object? frmMain object? What parameters does it need? I've read multiple MSDN articles and none of them have been of help, which is my reason for posting here. I'd not have posted if I hadn't researched aggressively. There's simply a concept that I'm missing which is key to unlocking this challenge.

              Comment

              • thanksinadvance
                New Member
                • Oct 2007
                • 5

                #8
                OK... So, I'm now back in the main thread... Here's what I added
                Code:
                Delegate Sub InvokeDelegate()
                and in the testEvent I added a call to another function
                Code:
                txtOutput.BeginInvoke(new InvokeDelegate(AddressOf updateText))
                Now I just need to figure out how to pass the parameters I have to the updateText function.

                Comment

                • Shashi Sadasivan
                  Recognized Expert Top Contributor
                  • Aug 2007
                  • 1435

                  #9
                  Dont complicate it so much
                  Try this first before you jump into delegates!
                  [CODE=cpp]if(InvokeRequir ed == true)
                  {

                  }
                  else
                  {
                  //update UI here
                  }[/CODE]

                  Comment

                  • PongaPundit
                    New Member
                    • Oct 2007
                    • 1

                    #10
                    Originally posted by thanksinadvance
                    OK... So, I'm now back in the main thread... Here's what I added
                    Code:
                    Delegate Sub InvokeDelegate()
                    and in the testEvent I added a call to another function
                    Code:
                    txtOutput.BeginInvoke(new InvokeDelegate(AddressOf updateText))
                    Now I just need to figure out how to pass the parameters I have to the updateText function.
                    Hi,
                    Invoke() as I understand (with my Win32 API programming background) posts message to the message queue associated with a window (i.e. a form).
                    So instead of invoking on txtOutput, try the form object:
                    Code:
                    frmObject.BeginInvoke(new InvokeDelegate(AddressOf updateText))
                    Replace frmObject above with your actual form object. It should work fine.

                    Comment

                    • balabaster
                      Recognized Expert Contributor
                      • Mar 2007
                      • 798

                      #11
                      In vb it would be done in the following way (assuming you are running an update on objects from within your form - i.e. this sub/delegate pair would both be in the codebehind for this form):

                      Code:
                          
                      Class MyForm
                      
                        Private Delegate Sub SetTextDelegate(ByVal InputStr As String)
                      
                        Private Sub SetText(ByVal InputStr As String)
                          If Me.InvokeRequired Then
                            Me.Invoke(New SetTextDelegate(AddressOf SetText), InputStr)
                          Else
                            Me.TextBox1.Text = InputStr
                          End If
                        End Sub
                      
                        Private Sub Clicked(ByVal Sender As Object, ByVal e As System.EventArgs) _
                        Handles MyButton1.Click
                          SetText("Hello World")
                        End Sub
                      
                      End Class
                      A slightly more complex example would be invoking this from another thread. The code is a little hokey, but it demonstrates the point (as well as demonstrates how to pass data into a thread at instantiation).

                      Code:
                      Public Class MyForm
                      
                          Private Delegate Sub SetTextDelegate(ByVal InputStr As String)
                      
                          Private Sub SetText(ByVal InputStr As String)
                              If Me.InvokeRequired Then
                                  Me.Invoke(New SetTextDelegate(AddressOf SetText), InputStr)
                              Else
                                  Me.TextBox1.Text = InputStr
                              End If
                          End Sub
                      
                          Private Sub MyWorkerThread(ByVal Sender As Object)
                              SetText(CType(Sender, String))
                          End Sub
                      
                          Private Sub Loaded(ByVal sender As Object, ByVal e As System.EventArgs) _
                          Handles Me.Load
                              Dim oThread As New System.Threading.Thread(AddressOf MyWorkerThread)
                              oThread.Start("Hello World")
                          End Sub
                      
                      End Class
                      In this manner it doesn't matter whether you call this routine from within your form directly or from another thread instantiated by the form. If you want to set the text property of your control you can invoke it using the same call:

                      Code:
                      SetText("Hello World")

                      Comment

                      • balabaster
                        Recognized Expert Contributor
                        • Mar 2007
                        • 798

                        #12
                        Um...just realised I didn't apply my example to your scenario...in your scenario, you would put the code to modify your 3rd party component in the Else statement in your "SetText" routine - I put "SetText" in quotes because obviously in your specific example it wouldn't be named that as the subroutine is being used for something quite different. I just used that as an abstract example to demonstrate the concept.

                        Comment

                        • Plater
                          Recognized Expert Expert
                          • Apr 2007
                          • 7872

                          #13
                          The Exception message details includes some statements that tell you exactly what to look up in msdn to find a way around this.
                          I cannot find it now since I don't have the Exception, but here is the example they provide:

                          Code:
                          private void SetText(string text)
                          {
                             // InvokeRequired required compares the thread ID of the
                             // calling thread to the thread ID of the creating thread.
                             // If these threads are different, it returns true.
                             if (this.myTextBox.InvokeRequired)
                             {
                                SetTextCallback d = new SetTextCallback(SetText);
                                this.Invoke(d, new object[] { text });
                             }
                             else
                             {
                                this.myTextBox.AppendText(DateTime.Now.ToString()+": "+ text);
                             }
                          }

                          Comment

                          Working...