Resolved A usercontrol on a form with long running method on BackgroundWorker

Post number 10 has been selected as best answer.

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
JohnH,


Private Sub UpdateUI()
Label1.text="1"
End Sub

I don't understand.
I see where the "Me." is implied.
Are you saying that instead of the above, to make it thread safe do:

Private Sub UpdateUI() 'form method
If InvokeRequired Then
Invoke(Sub() UpdateUI())
Exit Sub
End If
Label1.text="1"
End Sub
 
Last edited:

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
Sheepings,

I sorry but I missed seeing what I needed on the link (I know that does not needed it's not there).
I value all the help here but I'm a concerned I'm using an unfair amount of available resource.
Is really OK for me to include all the code (maybe 100 lines)?
 

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
VB.NET:
#Region "TabPage_11"

    Private mEditor As ControlTextEditor
    Private mLabelStateText_11 As String
    Private mSb__11 As New StringBuilder
    Friend WithEvents mBackgroundWorker_11 As System.ComponentModel.BackgroundWorker

    Private Sub ChangeCount_11(c As Integer)
        Label_Count_11.Text = CStr(c)

    End Sub

    Private Sub Button_RunBackgroundWorker_11_Click(sender As Object, e As System.EventArgs) Handles Button_RunBackgroundWorker_11.Click
        'Runs on main thread
        'BackgroundWorker_11 DoWork event is the result of calling RunWorkerAsync

        If mBackgroundWorker_11 Is Nothing Then

            mBackgroundWorker_11 = New System.ComponentModel.BackgroundWorker()
            mBackgroundWorker_11.WorkerReportsProgress = True
            mBackgroundWorker_11.WorkerSupportsCancellation = True
        End If
        If Not mBackgroundWorker_11.IsBusy Then

            If Not mBackgroundWorker_11.CancellationPending Then
                Button_CancelBackgroundWorker_11.Enabled = True
                mLabelStateText_11 = "INITIALIZING"
                Label_Count_11.Text = "0"
                Label_Max_11.Text = "0"
                mCount_11 = 0
                mSb__11.Clear()
                'Cause BackgroundWorker to raise the DoWork event: mBackgroundWorkerDoWorkHandler
                mBackgroundWorker_11.RunWorkerAsync(ControlRichTextBox_11)
                'Since I included an argument, RunWorkerAsync() will construct a DoWorkEventArgs object containing the argument,
                'Invoke the DoWork delegate asynchronously, And pass it the DoWorkEventArgs object as e And itself as sender.
            Else
                mLabelStateText_11 = "CAN NOT START - CANCEL PENDING"
            End If
        Else
            mLabelStateText_11 = "BACKGROUND DID NOT START"
        End If
        UpdateLableStateText()
    End Sub






 'If Invoke is required it invokes itself and for some reason it is then entered with InvokeRequired=False
 'I need to check to see: maybe when a sub is invoked, InvokeReuired is always false. I think that does makes sense.


    Private Sub UpdateUI() 'form method from JohnH
        If InvokeRequired Then
            Invoke(Sub() UpdateUI())
            Exit Sub
        End If
        Static z As Integer = 0
        Label2.Text = CStr(z)
        z += 1
    End Sub








    Private mCount_11 As Integer
    Private mSourceFiePaths As String()

    Private Sub BackgroundWorker_11_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles mBackgroundWorker_11.DoWork
        'Runs on BacgroundWorker Thread

        Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
        'Debug.WriteLine(ReferenceEquals(mBackgroundWorker_11, worker))  ' Shows True, so worker Not really needed here

        mEditor = DirectCast(e.Argument, ControlTextEditor)
        'Invoke a function on the UI thread
        'If the method will only be called from a non-GUI thread there's no point in using InvokeRequired

        mSourceFiePaths = DirectCast(ControlFolderAndFileExplorer_1_11.Invoke(Function() ControlFolderAndFileExplorer_1_11.qGetFileFullPathsFromSelectedFoldersTree), String())
        If mSourceFiePaths Is Nothing Then
            CancelThread()
            Exit Sub
        End If

        mLabelStateText_11 = "RUNNING"
        UpdateLableStateText()
        mBackgroundWorker_11.ReportProgress(1)
        Button_CancelBackgroundWorker_11.Enabled = True

        For Each fileFullPath As String In mSourceFiePaths
            If mBackgroundWorker_11.CancellationPending Then
                Exit Sub
            End If



  UpdateUI()



            Try
                'Run this sub on worker thread
                mSb__11.AppendLine(fileFullPath)
                mCount_11 += 1

                UpdateLableStateText() 'As it is it would slow the process
            Catch

            End Try

        Next

    End Sub

    Private Sub BackgroundWorker_11_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles mBackgroundWorker_11.ProgressChanged
        'Runs on main thread
        ' BackgroundWorker_11.ProgressChanged raised by running mBackgroundWorker.ReportProgress
        Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
        'Debug.WriteLine(mBackgroundWorker_11 Is worker) 'Shows True
        UpdateLableStateText()
    End Sub

    Private Sub Button_CancelBackgroundWorker_11_Click(sender As Object, e As EventArgs) Handles Button_CancelBackgroundWorker_11.Click
        'Runs on main thread
        CancelThread()

    End Sub

    Private Sub CancelThread()
        If mBackgroundWorker_11.WorkerSupportsCancellation Then
            If Not mBackgroundWorker_11.CancellationPending Then
                mLabelStateText_11 = "CANCEL PENDING"
                mBackgroundWorker_11.CancelAsync()
            End If

        End If
        UpdateLableStateText()

    End Sub

    Delegate Sub SetTextCallbackDelegateType(ByVal text As String)

    Private Sub SetText(ByVal text As String)
        mEditor.TxtText = text
    End Sub

    Private Sub mBackgroundWorker_11_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles mBackgroundWorker_11.RunWorkerCompleted
        'Runs on Main thread
        mLabelStateText_11 = "PREPARING TO DISPLAY"
        UpdateLableStateText()

        mEditor.TxtText = mSb__11.ToString

        mLabelStateText_11 = "DONE"
        UpdateLableStateText()

        'Using the Control.Invoke() method you may move the execution of a method or function from a background thread to the thread that the control was created on, which is usually the UI

        'A lambda expressions can be used to create a delegate method on the fly:

        'Control.Invoke Method Info

        'Invoke(Delegate)            Executes the specified delegate on the thread that owns the control's underlying window handle.

        'Delegate A delegate to a method that takes no parameters and contains a method to be called in the control's thread context.
        'Returns an object value from the delegate being invoked, or null if the delegate has no return value.

        'Invoke(Delegate, Object())        Executes the specified Delegate, On the thread that owns the control's underlying window handle, with the specified list of arguments.
        'A delegate to a method that takes parameters of the same number and type that are contained in the args parameter and contains a method to be called in the control's thread context.

        'Object()
        'An Array of objects to pass as arguments to the specified method. This parameter can be null if the method takes no arguments.

        'Returns An Object that contains the return value from the delegate being invoked, or null if the delegate has no return value.

    End Sub

    Private Sub UpdateLableStateText()
        If mSourceFiePaths Is Nothing Then Exit Sub

        'If the method will only be called from a non-GUI thread there's no point in using InvokeRequired
        'But this method is called from both threads by design

        'Accessing a sub with parameters
        'Without using a lambda expression creating a Delegate Is required
        If mEditor.InvokeRequired Then
            Dim setTextCallbackDelegateObject As New SetTextCallbackDelegateType(AddressOf SetText)
            mEditor.Invoke(setTextCallbackDelegateObject, New Object() {mSb__11.ToString})
        Else
            mEditor.TxtText = mSb__11.ToString
        End If

        'Accessing a sub with parameters
        If Me.InvokeRequired Then
            'A lambda expressions used to create a delegate method on the fly:
            Me.Invoke(Sub() ChangeCount_11(mCount_11))
        Else
            Me.Invoke(Sub() ChangeCount_11(mCount_11))
        End If

        If Me.InvokeRequired Then
            'A lambda expressions used to create a delegate method on the fly:
            'Accessing a set property
            Me.Invoke(Sub() Label_Max_11.Text = CStr(mSourceFiePaths.Length))
        Else
            Me.Invoke(Sub() Label_Max_11.Text = CStr(mSourceFiePaths.Length))
        End If

        'Accessing a set property
        If Label_State__11.InvokeRequired Then
            'If called by the worker thread
            Label_State__11.Invoke(Sub() Label_State__11.Text = mLabelStateText_11)
        Else
            'If called by the UI thread
            Label_State__11.Text = mLabelStateText_11
        End If
    End Sub
 
Last edited:

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
You don't need to use Invoke when using BackgroundWorker, and there is no reason to. This component exists to make it easy to run work in a thread and pass information back to UI thread.
All data that the thread needs can be passed to the argument parameter of RunWorkerAsync method.
Passing data from thread to UI during processing can be done with the userState parameter of ReportProgress method.
Any data that is the result of the operation can be passed to RunWorkerCompleted event using e.Result in DoWork handler.
Both ProgressChanged and RunWorkerCompleted events are raised in UI thread, so from these event handler methods no invoking is needed.

Mentioned argument/userState/e.Result is type Object, which means any kind of object can be passed. There is no limit, you can pass single value, an array, a collection, you can define a class with all kinds of properties and pass an instance of this through these. Don't pass controls or access controls from DoWork handler, pass data in, and pass data out. All interaction with controls can and must be done in UI thread, which excludes DoWork method.
 

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
113
Location
UK
Programming Experience
10+
I sorry but I missed seeing what I needed on the link (I know that does not needed it's not there).
I value all the help here but I'm a concerned I'm using an unfair amount of available resource.
Is really OK for me to include all the code (maybe 100 lines)?
You have two options.

  1. Run the 19 lines of code I told you to look at. And fit it in with your own project. If you chose the template below. Create a test app to test the code. Debug it, and learn how it works by reading up on documentation for whatever you don't understand. This is what I was pointing you to :
    Threading Template VB.Net:
     Public Delegate Sub Callback(ByVal s As String)
    
    Private Sub UpdateUI(ByVal this_Result As String)
    RichTextBox1.Text = this_Result
    End Sub
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim thread As New Thread(Sub() ExecuteWork())
    thread.Start()
    End Sub
    Private Sub ExecuteWork()
    'I am the working method, the method we called from UI thread.
    'I am not on the UI thread any more so i can't talk to it.
    Dim s As String = "Testing Worker Method"
    While True
    Debug.Write(s) 'This always runs and is running on non-ui thread
    End While
    'How to talk to your ui?
    RichTextBox1.Invoke(New Callback(AddressOf UpdateUI), s)
    'We call the control we MUST invoke, create a new delegate, send it the method to update from, and provide the value to that method.
    End Sub
    I would say that this way is probably simpler than using a background worker.
  2. And not to elaborate to much on what JohnH already said, but there is no need to call invoke when using a background worker, as already pointed out. Background worker has its own events to talk back to the UI while your worker is working. You just need to check periodically if progress changed and if it has BackgroundWorker.ReportProgress Method (System.ComponentModel) Had you read the docs I linked on p/#6, you would know how the background worker works and wouldn't be invoking at all. It is only in my example template is invoking required.
 

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
John,

There is no reason for this code other than to try out BackgroundWorker.
When I put your last suggestion in DoWork it was only to run it on the worker thread and see what it does.
I called UpdateLableStateText from both threads for the same reason.
That is true of most of the calls to the UI. I put Labels on the form simply to have something to access from worker.
The thing I needed was your suggestion to learn about Invoke, that suggestion opened a door.
I think I now know the things you mentioned in your last post.
Creating a class and passing it as an Object and then casting it at the other end, I did do when I first started playing with this code.
I'll look at the two new suggestions for background, but I think I did enough threading for a while.
I think might look at having an menuitem on a user control contextmenu and also a similar one on a form's menu and having to create two rather than cloning the usercontrol menuitem.

Thanks a lot for all the help!
 

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
Sheepings

I'm familiar with ReportProcess and ProgressChanged but I needed to test using Invoke so I did not use it much as it was intended to be used. This code is simply a test bed.

I like your spoiler code. I bet I'll be using it soon. I don't have enough insight to know why someone smarted than me generated BackgroundWorker but I have a feeling there was a good reason.

Maybe I'll take some graphics code I wrote and modify it using your template to run it on a thread and use invoke to update a progress bar control.
 
Last edited:

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
113
Location
UK
Programming Experience
10+
I'm familiar with ReportProcess and ProgressChanged but I needed to test using Invoke
If that where true, you wouldn't be using invoke. What you are doing is not part of the suggested documentation. That's why the background worker comes with events to allow you to talk back to your UI as already outlined.
Maybe I'll take some graphics code I wrote and modify it using your template
You don't need to take any such code form anywhere. The template works as i posted it. Remove the loop, as it was just an example to prove it was printing to the debug window in VS. As I suggested, create a new VB.Net project, paste the code, drag the named controls onto the form, rename them as they are in the code, then call the thread from the button click event and remember to set a breakpoint on the executing code to follow where the code goes and what it does.

If you are only testing for the craic, then you may as well use the template i gave you, because you are using the background worker incorrectly.

Happy coding!
 

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
Sheepings ,

I meant I have some code that could be improved by using a thread like you show. I've already run your code. I had to put a counter in the loop to stop it after a while.
 
Last edited:

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
113
Location
UK
Programming Experience
10+
You don't need this :
VB.NET:
While True
Debug.Write(s) 'This always runs and is running on non-ui thread
End While
Remove it, as it was there for example purposes.
 

aaaron123@roadrunner.com

Well-known member
Joined
Jan 23, 2011
Messages
47
Programming Experience
10+
VB.NET:
 While True
     If mAbort Then Exit While
End While
I added a button (button event not shown) to end the loop.

BTW, When I said (way above) I was testing it was probably not the best word to use. I didn't mean I want to be sure the app was OK. I was, in each instance, checking to see if some small amount of code worked the way I thought it would. The entire thing has no other useful purpose. Just playing.

Thanks again for the help.
 
Last edited:

Sheepings

Senior Programmer
Staff member
Joined
Mar 7, 2014
Messages
113
Location
UK
Programming Experience
10+
You're very welcome. Good you got it sorted.
 

matthewlkimes

New member
Joined
Jul 23, 2020
Messages
1
Programming Experience
1-3
The following example program creates a form that calculates Fibonacci numbers. The calculation runs on a thread that is separate from the user interface thread, so the user interface continues to run without delays as the calculation proceeds.

There is extensive support for this task in Visual Studio.
 

jmcilhinney

VB.NET Forum Moderator
Staff member
Joined
Aug 17, 2004
Messages
14,295
Location
Sydney, Australia
Programming Experience
10+
The following example program creates a form that calculates Fibonacci numbers. The calculation runs on a thread that is separate from the user interface thread, so the user interface continues to run without delays as the calculation proceeds.

There is extensive support for this task in Visual Studio.
There is no example.
 
Top Bottom