Link to home
Start Free TrialLog in
Avatar of Rick Becker
Rick BeckerFlag for United States of America

asked on

Backgroundworker fails in VB.net with "Cross-Thread operation not valid"

Hi All,

Ok I have a Function that needs to run in the background from the Same UI as the rest of the Program. I am using Visual Studio 2017 and VB.net.

I have been able to get most of the Function working in the background using the Backgroundworker control. This is a Very large function and it DOES need to periodically output some data to a TEXTBOX during its operation.

I know that I need to setup DELEGATES to workaround this Cross-Thread problem but I am only partially successful in my endeavors.  With the Delegate that I created I can only write to one TEXTBOX but not another..

So in my research I find that there is a method of turning off  the Checking of this condition.. I see that the option that I must set is 'Control.CheckForIllegalCrossThreadCalls. I understand that this is not recommended  but my  problem is that I Can Not find this Option to set...

Also if I try to use the 'INVOKE' method I cannot find the 'InvokeRequired' method which I believe is the reason  why my DELEGATE is only partially successful.

I would be grateful for help in either finding these options or in getting my delegates to work properly.
Obviously I will provide as much of my code as you wish  to see upon request.

Thanks in advance
Rick
Avatar of Chris Stanyon
Chris Stanyon
Flag of United Kingdom of Great Britain and Northern Ireland image

The BackgroundWorker has a ProgressChanged event which can be used to update the UI. From your background worker, you would call the ReportProgress() method, and that would fire the ProgressChanged event. In the event handler, you can update the UI without cross-threading issues. You can pass an Object into the ReportProgress() method and that object is in turn passed to the ProgressChanged event. You can access the properties of that object to update your UI
Hi Rick;

You need to implement the BackgroundWorker.ProgressChanged Event, That event runs in the same thread as the UI and so you will not get that exception. See the link below for examples of that.

BackgroundWorker Class
Avatar of Rick Becker

ASKER

Hi Chris..

OK let me see if I understand what you are saying...  at the moment I am using ReportProgress() and ProgressChanged() to update a progress bar following examples found on the internet. I update a ProgressBar at the end of a Very long For Loop. (see code snippets).

So If I understand you correctly you are saying that there is a way to pass some additional arguments to ReportProgress() and I can update Controls on the UI much like the ProgressBar is being updated now..  If this is true then I guess I need to figure out how to do that...

I guess I am hearing you say that I can call ReportProgress() at any time and that I can have as much code in the ProgressChanged()  sub routine as I need  to accomplish the Given task.... ??? True??

Question?? Can I pass a Structure to ReportProgress() that contains many Items that I need/want to change/update??

How do I do that??

Thanks in advance
Rick

                'check to see if this Process has been canceled
                If BackgroundWorker1.CancellationPending Then

                    BackgroundWorker1.ReportProgress(0)
                    e.Result = intI
                    e.Cancel = True
                Else
                    BackgroundWorker1.ReportProgress(intI)
                End If


                'EbayCDListingForm.ProgressBar3.Value = intI

Open in new window

Public Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
        ProgressBar3.Value = e.ProgressPercentage
        lblPercentProgress.Text = e.ProgressPercentage.ToString() + "%"
    End Sub

Open in new window

Hi All..

 ok I did a little test and I do see that I can  indeed update some controls from within the BackgroundWorker1_ProgressChanged() function, however I am not sure how to pass the information that I need to that function.  Chris you mentioned creating an object to contain the data that I need to update but I guess I must honestly say that I am not sure how to go about doing that...

The BackgroundWorker1.ReportProgress() function appears to accept a single argument and that is an Integer.... maybe you can help me understand how to implement that feature...

BTW here is that little piece of test code that I mentioned.. it worked but of course it did not output the data that I tried to pass to it..

and yes it is a meaningless piece of code..

Oh BTW... If I can just a STRING value passed to BackgroundWorker1_ProgressChanged() then I think that I can put some code together that will take care of my needs..

Thanks


Public Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged

        Dim SenderValue As String
        Dim SenderValueInt As Integer

        SenderValue = sender.ToString
        txtGetOrdersProcessInfo.Text = txtGetOrdersProcessInfo.Text + sender.ToString

        'ProgressBar3.Maximum = SenderValueInt
        ProgressBar3.Value = e.ProgressPercentage
        lblPercentProgress.Text = e.ProgressPercentage.ToString() + "%"
    End Sub

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Chris Stanyon
Chris Stanyon
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hey Chris...

Thanks a bunch ... I did not know about the over loads... that is  exactly what I needed... Let me put this all together now and I'll post the code showing how I used your suggestion/answer...

Don't sweat the syntax ... and yes it is all pretty much the same now so it's ALL Good ... hehe

Thanks Chris I'll post again in a few hours with some results
Also if I try to use the 'INVOKE' method I cannot find the 'InvokeRequired' method which I believe is the reason  why my DELEGATE is only partially successful.

You can Invoke() against any control, but the FORM is most convenient.

With that in mind, you can Invoke() from within your DoWork() handler to update the UI.  Be careful, however, that you only Invoke() small pieces of code that directly relate to updating the UI.  Do NOT put your entire DoWork() handler inside an Invoke() call since that will nullify the benefits of running on a different thread.

Simple example:
Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Dim recordNumber As Integer
        For i As Integer = 1 To 100
            System.Threading.Thread.Sleep(100) ' <-- simulated "work"
            recordNumber = i ' local variable used in anonymous sub below
            Me.Invoke(Sub()
                          Label1.Text = "Processing Record #" & recordNumber
                      End Sub)
        Next
    End Sub

End Class

Open in new window


There's nothing wrong with using the ProgressChanged() event as suggested by the others.  This is a matter of preference and code style.

This approach ~can~ be easier to understand since the code to update the UI is in the same method as our work in the DoWork() handler.
Hi Chris,

Well that worked just fine. Thank you very much... as promised here is the code showing what I did. I simply created a sting containing the name of the control that I needed to update separated with a Pipe (|) delimiter and then the data...  I then Parsed  (Split)  the string and used it in a Select-Case statement.. Works just fine..

Rick

ThisReviseListingString = "Revise" + vbTab + ItemIDArray(ArrayIndex) + vbTab + NextReviseSku + vbTab + "2.11" + vbTab + "1" + vbNewLine
                        objWriter.Write(ThisReviseListingString)

                        ' Now Ceate the Revise Listing Record
                        BackGroundControlString = ""
                        BackGroundControlString = "txtGetOrdersProcessInfo" + "|" + ThisReviseListingString.ToString
                        BackgroundWorker1.ReportProgress(intI, BackGroundControlString)

Open in new window


Public Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged

        Dim UserStateString As String
        Dim UserStateArray() As String

        UserStateString = e.UserState
        UserStateArray = UserStateString.Split("|")

        Select Case UserStateArray(0)
            Case "txtGetOrdersProcessInfo"
                txtGetOrdersProcessInfo.Text = txtGetOrdersProcessInfo.Text + UserStateArray(1).ToString
                txtGetOrdersProcessInfo.Refresh()

            Case "ProgressBar3.Maximum"
                lblMaxNumOrders.Text = UserStateArray(1)
                ProgressBar3.Maximum = CInt(UserStateArray(1))

            Case "ProgressBar3.Value"
                ProgressBar3.Value = CInt(UserStateArray(1))

            Case "txtStandardListingFileName"
                txtStandardListingFileName.Text = UserStateArray(1).ToString

            Case "lblNumOrdersProcessed"
                lblNumOrdersProcessed.Text = UserStateArray(1)

        End Select

        lblPercentProgress.Text = e.ProgressPercentage.ToString() + "%"

    End Sub

Open in new window

Hi Mike...

Thanks for the response It is very much appreciated...

I was busy posting my answer to Chris when you Posted so I did not see your post until later. Your suggestion is good but I have a Very large piece of code that I needed to run in the background I am not sure that it would benefit  much from your suggestion but quit frankly I have NO idea if it  Would or Would Not..  

I have a working solution in place now and it seems to work fine...  I am accepting Chris's solution but I will keep your idea in mind if things don't work well after time..

Thanks

Rick
Thanks to All that assisted in this solution. I very much appreciate your time and your efforts..

Rick
Glad you got it working Rick :)
No problem.  Good luck!