Write to textbox.text while running backgroundworker VB.net - vb.net

Hi so i have small form application
the problem was that when i was running tasks on the main thread, the program window wasn't responding
so my button redirects the task to the backgroudworker:
Private Sub btn_start_Click(sender As Object, e As EventArgs) Handles btn_start.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Now i want to output to my textbox (txtlog) some text from this Backgroudworker multiple time in one Sub. I found a solution that allows this. but this makes the code quite ugly if I have to use it several times:
Example
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
If txtLog.InvokeRequired Then
txtLog.Invoke(Sub() txtLog.AppendText("My text" & vbNewLine))
txtLog.Invoke(Sub() txtLog.ScrollToCaret())
Else
txtLog.AppendText("My text" & vbNewLine)
txtLog.ScrollToCaret()
End If
Something in Backgroudworker . . .
If txtLog.InvokeRequired Then
txtLog.Invoke(Sub() txtLog.AppendText("My text" & vbNewLine))
txtLog.Invoke(Sub() txtLog.ScrollToCaret())
Else
txtLog.AppendText("My text" & vbNewLine)
txtLog.ScrollToCaret()
End If
. . .
End Sub
Is there any shorter method that would allow me to write in textbox?

The whole point of using a BackgroundWorker is so that you don't have to call Invoke and use delegates. If you're going to do that anyway then the BackgroundWorker is pointless.
With regards to the code you have, what's the point of testing InvokeRequired when you know for a fact that the DoWork event handler will be executed on a background thread? It's always going to be True. Also, why would you call Invoke twice and add all that overhead when you can call it once and do as many things as you like in the one call?
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
txtLog.Invoke(Sub()
txtLog.AppendText("My text" & vbNewLine))
txtLog.ScrollToCaret()
End Sub)
As I said though, you shouldn't be calling Invoke at all. If you want to do something on the UI thread from a BackgroundWorker then you call ReportProgress and handle the ProgressChanged event. The code you want executed goes in the event handler and you call ReportProgress as required. If you need to pass data, you do so using the second argument to ReportProgress and get it back from e.UserState in the event handler. Here's one I prepared earlier:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Raise the DoWork event in a worker thread.
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
'This method is executed in a worker thread.
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim worker As System.ComponentModel.BackgroundWorker = DirectCast(sender, System.ComponentModel.BackgroundWorker)
For i As Integer = 1 To 100
'Raise the ProgressChanged event in the UI thread.
worker.ReportProgress(i, i & " iterations complete")
'Perform some time-consuming operation here.
Threading.Thread.Sleep(250)
Next i
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.ProgressBar1.Value = e.ProgressPercentage
Me.Label1.Text = TryCast(e.UserState, String)
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
Me.Label1.Text = "Operation complete"
End Sub

Related

BackgroundWorker. Stop thread execution

Good afternoon!
Can you tell me how to stop the execution of a thread? The following code doesn't work:
Private Sub Stop_Click(sender As Object, e As EventArgs) Handles Stop.Click
If BackgroundWorker1.IsBusy Then
If BackgroundWorker1.WorkerSupportsCancellation Then
BackgroundWorker1.CancelAsync()
End If
End If
End Sub
That code does exactly what it is supposed to do, i.e. REQUEST a cancellation. It can't just stop working though, because it has no idea what work is being done. YOU are the one writing the code to do the work so YOU are the one who has to write the code to check whether a cancellation has been requested and to actually perform that cancellation. We have no idea what work you're doing so we have no idea where it is convenient to check whether a cancellation has been requested and what, if any, clean-up may be required in that case. Here's a basic example of what a cancellation might look like though:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Raise the DoWork event in a worker thread.
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
'This method is executed in a worker thread.
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim worker As System.ComponentModel.BackgroundWorker = DirectCast(sender, System.ComponentModel.BackgroundWorker)
For i As Integer = 1 To 100
If worker.CancellationPending Then
'The user has cancelled the background operation.
e.Cancel = True
Exit For
End If
'Raise the ProgressChanged event in the UI thread.
worker.ReportProgress(i, i & " iterations complete")
'Perform some time-consuming operation here.
Threading.Thread.Sleep(250)
Next i
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, _
ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.ProgressBar1.Value = e.ProgressPercentage
Me.Label1.Text = TryCast(e.UserState, String)
End Sub
'This method is executed in the UI thread.
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
'The background operation was cancelled.
Me.Label1.Text = "Operation cancelled"
Else
'The background operation completed normally.
Me.Label1.Text = "Operation complete"
End If
End Sub
Private Sub Button1_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles Button1.Click
'Only cancel the background opertion if there is a background operation in progress.
If Me.BackgroundWorker1.IsBusy Then
Me.BackgroundWorker1.CancelAsync()
End If
End Sub

Background Cancle Event Form Closing Error

I have a background event that is consistently withdraws data. When I close the form background event doesn't cancel for some reason. If I enable a message in between it does...... I tried to put a time pause, REFRESH, and both. But its only works properly after the message box. I don't really understand why, since button it self to cancel works fine when form is not close. ERROR "Cannot access a disposed object.Object name: 'Form1'." Somehow it doesn't see "False" on closing.
Thanks!
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
RED(False)
'Some other functions - WORK OK
End Sub
Private Sub RED(ByVal reads As Boolean)
bck.WorkerSupportsCancellation = True
If reads = True Then
bck.RunWorkerAsync()
ElseIf reads = False Then
bck.CancelAsync()
bck.Dispose()
End If
End Sub
Private Sub bck_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bck.DoWork
Do
If bck.CancellationPending = True Then --- DOESN'T look like see this without Message BOX
Exit Sub
End If
Me.Invoke(Sub()
'EVENT HERE WERE I AM GETTING AN ERROR --- HOWEVER, Works as a button when called on RED(False) and Works if I put a message BOX
End Sub)
Loop
End Sub
It would appear to be a matter of timing, as is often the case with multiple threads. What I would suggest is that, if the form is closed when the background task is in progress, you cancel the background task and then cancel the close. You can then close again when the background task has completed, e.g.
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Do
If BackgroundWorker1.CancellationPending Then
'Cancel the background work.
Exit Do
End If
'Do the background work.
'...
Loop
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
'Close the form.
Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If BackgroundWorker1.IsBusy Then
'Cancel the background task.
BackgroundWorker1.CancelAsync()
'Do not close the form this time.
e.Cancel = True
End If
End Sub
If you won't always want to close the form when the background task completes then you can indicate that to the RunWorkerCompleted event handler by, for instance, setting e.Result in the DoWork event handler and then testing it in the RunWorkerCompleted event handler, e.g.
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
e.Result = False
Do
If BackgroundWorker1.CancellationPending Then
e.Result = True
'Cancel the background work.
Exit Do
End If
'Do the background work.
'...
Loop
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If CBool(e.Result) Then
'Close the form.
Close()
End If
End Sub
Use PROGRESS REPORT instead of Invoke.
Private Sub bck_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bck.DoWork
Do
If bck.CancellationPending = True Then
Exit Sub
End If
bck.ReportProgress("WHATEVER YOU CAPTURING/VALUES UPDATE")
Loop
End Sub
Private Sub bck_dProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bck.ProgressChanged
YOUR DISPLAY = ("WHATEVER YOU CAPTURING/VALUES UPDATE")
End Sub

Access Main thread data from the Background thread

I´m using a backgroundworker to handle processing of data while the user is still free to for instance click another button that aborts the process.
However, the code in backgroundworker requires several pieces of data, for instance it needs to know whether a radio button is checked.
Is there a way to access data in another thread from the background thread? Or should I create global variables that hold this information?
Public Class Test
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Button1.Content = "Working..."
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
If RadioButton1.IsChecked Then
MsgBox("It works")
End If
End Sub
End Class
You can pass the RadioButton checked state to the RunWorkerAsync method like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync(RadioButton1.Checked)
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim checked As Boolean = CBool(e.Argument)
If checked Then
MessageBox.Show("It works")
End If
End Sub
Let me know if you need to be checking the RadioButton continuously from inside a loop, as that would be different.

RichTextBox not being updated by a Delegate / BackGroundWorker

I have a RichTextBox on a Windows Form that I wish to update with 'checkpoints' as I go through an XML building process.
I have a Class called 'LogFiles' that has the Delegate for the RichTextBox
Public Delegate Sub AppendRichTextBoxDelegate(ByVal RTB As RichTextBox, ByVal txt As String)
In that Class I have the Sub to Append the RTB
Public Shared Sub AppendRichTextBox(ByVal RTB As RichTextBox, ByVal txt As String)
Try
If RTB.InvokeRequired Then
RTB.Invoke(New AppendRichTextBoxDelegate(AddressOf AppendRichTextBox), New Object() {RTB, txt})
Else
RTB.AppendText(txt & Environment.NewLine)
End If
Catch ex As Exception
MsgBox(MsgBox(ex.Message & Environment.NewLine & Environment.NewLine & ex.StackTrace))
End Try
End Sub
Now when I call my BackGroundWorker
Public Sub BGW1ProcessFile_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BGW1ProcessFile.DoWork
LogFiles.AppendRichTextBox(LogFileRTB, "Starting")
LogFiles.CreateLog()
Interchange.StartXML()
End Sub
That AppendRichTextBox works as expected and updates the RTB in real time, HOWEVER when I then stop into 'Interchange.StartXML' that also contains an AppendRichTextBox it does not update the RTB from within that Sub.
When the BackGroundWorker finishes the AppendRichTextBox for for the RunWorkerCompleted Event works as expected.
Why does the AppendRichTextBox Sub I have created with the delegate work on the BackGroundWorker Process but does not work on the Sub that is launched as part of the BackGroundWorker Process?
Its pickling my head a little bit, any assistance would be appreciated.
Regards,
James
As explained via comments, the BackgroundWorker is precisely meant to deal with the 2-thread situations involving GUI + another thread. That's why its in-built functionalities can gracefully account for the most likely situations.
In your case (i.e., regularly updating GUI elements from the BGW thread), you should rely on the ProgressChanged event. Here you have a sample code clearly showing how to use it:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BGW1ProcessFile.WorkerReportsProgress = True
BGW1ProcessFile.RunWorkerAsync()
End Sub
Private Sub BGW1ProcessFile_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BGW1ProcessFile.ProgressChanged
'You can include here as complex modifications on the GUI as
'required. Just store anything in e.UserState, as shown below
RTB.Text = RTB.Text & e.UserState.ToString & Environment.NewLine
End Sub
Private Sub modifyRTB(caseNo As Integer)
'Here you are calling the aforementioned ProgressChanged event.
BGW1ProcessFile.ReportProgress(0, "This is case " & caseNo.ToString)
End Sub
Private Sub BGW1ProcessFile_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BGW1ProcessFile.DoWork
BGW1ProcessFile.ReportProgress(0, "We are in the BGW thread now")
modifyRTB(1)
modifyRTB(2)
modifyRTB(3)
End Sub
Private Sub BGW1ProcessFile_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BGW1ProcessFile.RunWorkerCompleted
RTB.Text = RTB.Text & Environment.NewLine & "We are outside the BWG thread now"
End Sub

How to use Backgroundworker to load form?

When I try to load a form with a backgroundworker class, it throws an exception because I tried to access an object that was created by another thread, not by the current thread. How to load that form by backgroundworker ?
(I am looking for marquee progress bar while the form is loading)
You can't change any controls in the DoWork section of a backgroundworker since it's a separate thread. You'll want to process any data in the DoWork section, then update your form in the RunWorkerCompleted section.
Example:
Private WithEvents bgwTasks As New BackgroundWorker
Private Sub bgwTasks_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwTasks.DoWork
e.Result= GetDatabaseTable()
End Sub
Private Sub bgwTasks_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwTasks.RunWorkerCompleted
If e.Result IsNot Nothing Then
DataGridView1.DataSource = e.Result
End If
End Sub
Private Sub Main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If bgwTasks.IsBusy = False Then
bgwTasks.RunWorkerAsync()
End If
End Sub
This will disable checking for Cross Threads
Me.CheckForIllegalCrossThreadCalls = False
I still suggest you search for Cross Thread regarding the proper use of the BackgroundWorker Class.
There is no problem to initialize the second form in another thread, but if you want to use it in the context on your main form, you have to use original thread. The follows code creates and initializes new form in background worker and then shows it when initialization is completed in appropriate event handler:
Public Class Form1
Dim form2 As Form2
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
form2 = New Form2()
form2.TextBox1.Text = "Text"
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
form2.Show()
End Sub
End Class
You can use ProgressChanged event of background worker for the purpose of report to progress bar.
You cannot do this. Forms have Thread-Affinity. ie. they can ONLY be accessed by the thread that instantiated the Form. You background thread just became the UI thread for the form!
So what you MUST do is marshal the call onto the correct thread.
Test InvokeRequired. If true, instantiate a delegate, call BeginInvoke and IMMEDIATELY return.
Now the call will be marshaled to the correct thread.