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

aaaron

Well-known member
Joined
Jan 23, 2011
Messages
216
Programming Experience
10+
I have a usercontrol on a form that contains a long running method.

I need the usercontrol to show on the form for the user to click something on it.

I also need to run a method that is on the usecontrol from a BackgroundWorker that is created by the form.

I think I'm up a creek without a paddle but 've seen magic o this site before so I thought I'd ask.

Is there any way??

Edited after the two replies.

Is this different than what I said before appeared to say?
 
Last edited:
The complexity of the code is irrelevant. It's a very simple principle. Don't access the UI in code executed on a secondary thread. There are some control members that you can access with there being an issue but it's not easy to know which ones so the best thing is to just avoid any. If you're executing code on a secondary thread and you want to access the UI, you need to marshal a method call to the UI thread to do so. That's what the Invoke method does, which you ought to know by now because I already suggested that you look it up. The BackgroundWorker already provides a way to update the UI, i.e. the ReportProgress method and the ProgressChanged and RunWorkerCompleted events. They don't help you get data from the UI during the background work though.

You could also dump the BackgroundWorker altogether and use the Async/Await pattern, but that will take some research too.
 
Take a look at the first code block only here : Answered - Read or get cell value (Excel)

That might give you an idea on how else you can do what you need. However, this method I provided works very similarly to what has already be explained to you above by using a background worker.

I suggest looking up the background worker documentation which I've already linked you on page #6 - You have no excuses now providing you read what has been given to you already, and that included all the docs on the background worker too. Until you actually try something, which involves altering your code as already explained to you, there is little more to explain, or little more we can do to help you. Post back when you make some changes?
 
Thanks. This is what I came up with. I would guess there is room for improvements. If it doesn't look useful its because I'm just a hobbyist interested in checking out things like this out so I try to use many variations. Unless I'm given reason to continue with this I'll move on to something else now.

Sorry I missed the Invoke statement in post 10. The only excuse I have is maybe because it was in bedded in black I subconsciously skipped over it like I skip over ads (best excuse I could come up with :)


VB.NET:
 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() {sb__11.ToString})
        Else
            mEditor.TxtText = sb__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:
A simpler pattern you can use:
VB.NET:
Private Sub UpdateUI() 'form method
    If InvokeRequired Then 
        Invoke(Sub() UpdateUI())
        Exit Sub
    End If

    'normal code, always in UI thread here.
End Sub
InvokeRequired and Invoke refers to current instance here. If called from different thread the method invokes itself on UI thread.
 
The Implementation on p/#18 is very vague without seeing the whole example the OP wrote from thread start, to delegating back to the UI.

@OP I linked you that so you could get an understanding of how to navigate information back to your UI when running on a new thread as shown in my template code. You should also note; the background worker has a lot of added benefits too as explained on p/#16 regarding updating the UI from a background worker. While in theory they both do the same thing, its your call which implementation to use.

I would like to see your full implementation though, if you can share it?
 
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:
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)?
 
insertcode.png
 
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:
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.
 
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.
 
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!
 
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:
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!
 
Back
Top