Cross-thread call problem

mmiller039

Member
Joined
Dec 16, 2008
Messages
8
Programming Experience
Beginner
I have a form that is built dynamically with the user adding a sets of textboxes relating to a spread (financial fututres). The structure is that I have a class (spread), that consists of 2 textboxes and a number of other components. In my main method I have an array to hold each instance of the spread as it is created. I get information coming in that relates to how I calculate the value in the two textboxes, since I want these to keep "updating" I tried creating a background thread that constantly monitors for a change in the value and updates the two texboxes. I then received a Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on and reading the help I then created a backgroundworker to handle the same process, but have come up with the same error. Any help would be greatly appreciated, have added the code of the backgroundworker, problem occurs on the indA = getInt.getIntIndex(spreadList(spreadNumber).indexA.Text)

VB.NET:
    ' Start one backgroundworker to handle bid/ask updates on all the spreads
    Private Sub callBAThreader()
        MonitorThread = New BackgroundWorker
        MonitorThread.RunWorkerAsync()
    End Sub


    Private Sub _DoWork(ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) Handles MonitorThread.DoWork
        While True
            For i = 0 To numSpreads - 1
                If spreadList(i).onOff.Text = "On" And spreadList(i).bid.Text <> "" And spreadList(i).ask.Text <> "" Then
                    updateBidAsk(i)
                End If
            Next
        End While
    End Sub


    Public Sub updateBidAsk(ByVal spreadNumber As Integer)
        Dim indA, indB As Integer
        Dim preBid, preAsk As Double

        indA = getInt.getIntIndex(spreadList(spreadNumber).indexA.Text)
        indB = getInt.getIntIndex(spreadList(spreadNumber).indexB.Text)

        preBid = 100 * (Convert.ToDouble(startNum(indA, 5)) - _
                   Convert.ToDouble(startNum(indB, 4)))
        preAsk = 100 * (Convert.ToDouble(startNum(indA, 4)) - _
                   Convert.ToDouble(startNum(indB, 5)))
        If preBid < 10 And preAsk < 10 Then
            spreadList(spreadNumber).bid.Text = Format(preBid, "0.00")
            spreadList(spreadNumber).ask.Text = Format(preBid, "0.00")
        Else
            spreadList(spreadNumber).ask.Font = New Font("Microsoft Sans Serif", 8.75, FontStyle.Bold)
            spreadList(spreadNumber).ask.Font = New Font("Microsoft Sans Serif", 8.75, FontStyle.Bold)
            spreadList(spreadNumber).bid.Text = Format(preBid, "00.00")
            spreadList(spreadNumber).ask.Text = Format(preBid, "00.00")
        End If
    End Sub
 
You must not access any windows control from within the DoWork code. If you have things that DoWork must read about the display, write them to a class-wide variable every time they change (attach a textchanged handler and update a string variable. In C# I would mark this variable as volatile, I dont know what the VB equivalent is) and have DoWork read those instead of the controls.

When it comes to writing to the UI, again you must not do this from Do_Work.

I ususally abuse backgroundworker.ReportProgress(int, object) for this, passing an int that indicates what I want to change and obj as what I want it changed to:

VB.NET:
Sub Bgw_DoWork()

  .. code

  If textBox1NeedsUpdating Then
    bgw.ReportProgress(1, "Hello")
  ElseIf textBox2NeedsUpdating Then
    bgw.ReportProgress(2, "World")
  End If
End Sub

Sub Bgw_ReportProgress(sender, args)

  If args.ProgressPercentage = 1 Then
    textBox1.Text = args.UserState.ToString()
  ...
End Sub

ReportProgress handler automatically executes on the same thread that the BGW was created with which SHOULD be (unless you created it in another thread) the same thread that created your controls, hence it works

If you have a lot of things to update and dont want to write an if-else for all of them, you can pass the control you want updating in the object too:

bgw.ReportProgress(0, New Object(){ myTextBox, "my value" } )
...
ReportProgress Handler:
'you could use a custom class here to hold the control and the value
DirectCast(DirectCast(args.UserState, Object())(0), Control).Text = DirectCast(args.UserState, Object())(1).ToString()


-


You can also write a method that sets the text of a control using Invoke; the control has a reference to the thread that created it and calling Invoke on the control causes that thread to run the specified delegate. I find this approach slightly more messy if background worker is already in use and has a mech for handlign the invokes
 
Back
Top