Backgroundworker - UI still not responsive until BGW completed - vb.net

I implemented a BGW to my application in hopes of speeding up loading time for the starting window(40+ controls)
I will not post my whole code as it's far too long but will give you the gist of the code. I split big function calls that take time to complete alongwith a handful of controls and moved them into the BGW in hopes of asyncronously loading controls to help quicken the process.
It is understood that I have to move UI changing code to the ProgressChanged event or RunWorkerCompleted event, which I have done. I originally had all code thrown into the DoWork event and it was extremely fast but found out it's not safe so I had reworked it to move all UI-related oode to the ProgressChanged. It's not nearly as fast now and it seems that the BGW controls wait until the UI thread completes prior to changing the controls in the BGW_ProgressCHanged event. I never saw this 'lag' between the two when I had all the changes in DoWork. What can I do about this? Or can I at least had the BGW update the controls realtime rather than waiting for all controls are completed before updating all controls?
The responsiveness is lower as well as it locks up the window to wait for the BGW controls to update. Just looking for what could possibly be happening
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
SyncLock <globalVar>
BackgroundWorker1.ReportProgress(0, "Tom")
End SyncLock
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim value As String = DirectCast(e.UserState, String)
Select Case e.ProgressPercentage
Case 0
lblName.text = value
lblName.Visible = true
End Select
End Sub

You removed all evidence of the problem in your code, but the diagnostic is an excellent match. You have a fire hose problem. Your code is calling ReportProgress far too often.
Things go wrong when your ProgressChanged event handler needs more time than the time between ReportProgress calls. Which is like drinking from a fire hose, no matter how fast you swallow the water, you just can't keep up with the flow.
Which is what the UI thread experiences. When it finishes the call to your ProgressChanged event handler, there's yet more water, yet another request to call the handler. That relentlessly continues without the UI thread ever being able to keep up. It now doesn't get around to doing its normal duties anymore. Which means that your UI stops updating, paints are no longer performed. And it doesn't respond to input anymore.
This can last for a while after the worker thread stopped running, the UI is still trying to work down the backlog of requests. When it finally gets there, the UI suddenly springs back to life.
A simple way to diagnose this condition is to add this method call after the ReportProgress call:
System.Threading.Thread.Sleep(45)
Which slows down the worker thread enough to limit the number of ReportProgress() calls to no more than 21 per second. Which is plenty fast enough for human eyes, anything faster just looks like a blur so is wasted effort. If that fixes the problem then you found the cause.
Using Sleep() like this is otherwise an ugly Q&D solution for the problem, it of course also slows down your worker so its gets less work done. You'll have to improve your code so that this doesn't happen and just makes less ReportProgress calls.

One thing you might want to add before starting the worker:
Me.SuspendLayout()
Then, in the RunWorkerCompleted event:
Me.ResumeLayout()
That should suspend all layout logic until all the work is done, then update the entire form in 1 operation.
EDIT
Replace
BackgroundWorker1.ReportProgress(...)
With
DirectCast(sender, BackgroundWorker).ReportProgress(...)
And get rid of the synclock.
EDIT2
The correct way to feed the data to the DoWork event is through the DoWorkEventArgs.
Start work like this:
BackgroundWorker1.RunWorkerAsync(<globalvar>)
And access it like this:
Dim globalVarData As Object = e.Argument

Related

Why Async Callback in System.Windows.Forms.Timer execute the await more times?

I have a windows forms timer with an async callback that await a database operation, why the await is executed again even before the previous callback has not finished yet? How can I fix this?
Many people say that System.Windows.Forms.Timer wait that previous callback operation finishes before executing new one.
Private Async Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
' I have an array with 6 elements that i control
' for executing database operation
For i=1 To 6
If(some condition is true in array index)
Await SaveAsync()
'set to false the condition in array index after save
End If
Next
End Sub
Thanks in advance.
It is presumably because the asynchronous call takes longer than the Interval of your Timer. That would mean the Tick event would be raised again before the previous event handler has finished executing. If what you want is a specific interval between when the previous call has completed and the next event then you should stop the Timer, make the call and then start it again:
Private Async Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Timer1.Stop()
Await SaveAsync()
Timer1.Start()
End Sub
The other answer tells you how to fix the problem, but it doesn't explain what's going wrong.
There is a much older Stack Overflow question about the timer options, and it makes a possibly misleading statement about the timer tick waiting. It might be more helpful to discuss the potential implementations of a timer tick:
A new thread can be spun up to service the tick immediately
A notification can be posted which is processed at a later time
System.Windows.Forms.Timer does the latter. In particular, it posts the notification as a Windows message, and when the standard message pump sees the message, it raises a .NET event which is processed on the UI thread.
In most cases, this will mean that processing one tick will prevent anything from happening for the next tick, because the message pump is blocked while the event handler executes.
This only holds as long as the message pump is blocked, though. There are two likely ways it might become unblocked:
Code to explicitly process messages, like a call to Application.DoEvents or equivalent code written directly against the dispatcher
Await will do this implicitly, as it returns control to the UI will it waits for the asynchronous call to finish. This is by design and is one of the intended use cases for Await, to allow the UI to be responsive while the asynchronous call is executing.
As in any case where you use Await, you need to be prepared for re-entrancy and design your code accordingly.

VB.NET Invalid cross-thread access

I have a background worker control on a form.
In this form I have another form that shows a progress:
Private _fWait As frmWait
I am updating this form, change its label to tell the user what is currently going on.
When the background worker is finished, I want to close this form _fWait.
I am using
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'do the background worker stuff. I have not stated it here because it is not important
_fWait.Close()
_bDone = True
End Sub
But I am getting the error "Invalid cross-thread access: The access to the control frmWait occured from a diffent thread than the thread it was created by." on the line "_fWait.Close()".
Can somebody tell me what I did wrong?
Thank you!
When you call _fWait.Close() is has to be called on the UI thread whereas the BackgroundWorker1_DoWork handler will be running on a different thread which is why you are getting the error. There are two options for closing the form:
1) Continue to close the form on the DoWork handler but marshal the request onto the UI thread using something like the code below:
this.Invoke(() => _fWait.Close());
It's been a while since I've done VB, so you'll have to convert the C# code into VB...sorry.
2) Handle the RunWorkerCompleted event and close it there. If you BackgroundWorker was started on
the UI thread, then the RunWorkerCompleted will also be called on the UI thread.
If I remember right the ProgressedChanged event will also be called on the UI assuming that the BackgroundWorker was created on the UI thread. If the BackgroundWorker is created another another thread, then the RunWorkerCompleted and ProgressChanged will also be called on a separate thread and you will have to marshal calls to the UI as described above in step 1.
In simple programs, you can ignore cross-thread errors by adding this to your Form.Load method:
CheckForIllegalCrossThreadCalls = False
Note that it can cause problems when running multiple asynchronous threads that access shared data, so don't use it indiscriminately.
If multiple threads cause a shared routine to run, use SyncLock to prevent multiple simultaneous instances.

VB.net event handler abort when it is fired again

I have an application written in VB.net. In that application I have multiple forms and lots of functionality. The form the application starts with, is some kind of menu. In the background, I have a list of menu items that a user can see and use to open a new form. It is possible to search through all those menu items via a textbox where you can fill in some text and the code then compares all the menu items names to the filled in text and shows the result. This event is fired on every textchanged event of this textbox. But if the user types in a name that occures a lot (like à 100 times or so) the view takes some time (3 to 5 sec) to display all those results. Now I would like to know if it is possible to abort the first event handler if the same event is called again. That means that if I am typing in the textbox and for the first 4 or 5 letters almost all menu items are matches, so I want to abort that search and start a new one right away. Is there any way to detect that the same event is called again and abort the currect one to make the new one start right away?
Thanks in advance for reading this and helping me solve this problem!
In order to accomplish this, you are going to have to do the work in a separate thread. The primary reason for that is that WinForms are single-threaded. All UI-related events in a WinForm are handled on the same UI thread. As such, there is no way for the TextChanged event to fire again while you are still in the middle of processing the previous event. The UI will be locked up until the first event if finished processing.
However, if you do all of the menu-filtering work in another thread, then your UI will be freed-up to react to user input while you are doing the work. Then your TextChanged event will be allowed to fire before the previous one is done processing.
The easiest way to implement multi-threading in a WinForm project is to use the BackgroundWorker component. You can find it in the form-designer tool box. Luckily, the BackgroundWorker component has some properties and methods which are useful for implementing the cancellation as you described.
For instance, here's a very simple example. In this example, every time the text in TextBox is changed, it starts BackgroundWorker1 performing some work. The work that it does is to simply wait two seconds and then copy the contents of TextBox1 to TextBox2. If the text changes again before those two seconds are complete, it cancels the bacground work and starts it again from the beginning.
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If BackgroundWorker1.IsBusy Then
BackgroundWorker1.CancelAsync()
Else
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 20
If BackgroundWorker1.CancellationPending Then
e.Cancel = True
Exit Sub
End If
Thread.Sleep(100)
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If e.Cancelled Then
BackgroundWorker1.RunWorkerAsync()
Else
TextBox2.Text = TextBox1.Text
End If
End Sub
In order for the above example to work, the BackgroundWorker1.WorkerSupportsCancellation property must be set to True.
As you can see, when the text changes, it simply checks the IsBusy property, which determines whether or not the background thread is still working from a previous event. If it is, it cancels it. If it's not, it starts it.
All of the work which needs to be done on the separate thread is done inside the background worker's DoWork event handler. As it is doing the work, it needs to periodically check whether or not it has been canceled. If it has been canceled, it needs to stop what it's doing and set the Cancel property of the event args to indicate that it is stopping because it was canceled.
Once the background work is done, (whether by cancellation or by completing its task), the background worker raises the RunWorkerCompleted event. The event args for that event have a Cancelled property which indicates whether or not the work completed because it had been canceled prematurely. In the example, if it was canceled, it simply restarts the work from the beginning.
For what it's worth, all of this would be moot if there was some way for you to speed up the menu-filtering algorithm to the point where it's near instantaneous. It may be possible to do that by indexing your menus in something like a suffix array.
Try to add a condition. In your search method, if your text length exceeds some value, call the same event again (like in recursive methods). It should works.
Regards,
Daniel
add some condition on length of search string...like on its Length should be 5 or more
OR maximum results shown at one time should be limited.

Visual Basic 2008 - New Form on a different thread

My “form1” is just a simple page with buttons that launch different forms which do all the work, the "form1" code for the first four buttons is below.
What I want is for each form to run in a separate thread.
Public Class Main
Private Sub btnDownLoadStockPrices_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDownLoadStockPrices.Click
LoadStocksFromDownloadSite.Visible = True
End Sub
Private Sub btnLoadOptionsIntoDatabase_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoadOptionsIntoDatabase.Click
LoadOptionsIntoDatabase.Visible = True
End Sub
Private Sub btnVerifyDatabases_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnVerifyDatabases.Click
VerifyDatabase.Visible = True
End Sub
Private Sub btnAnalyzeStock_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAnalyzeStock.Click
AnalyzeSingleStock.visible = True
End Sub
End Class
I’ve found plenty of code examples to have different buttons on a single form run a subroutine in a separate thread, but for some reason I can’t seem to apply it to an entire form.
I think it’s something simple, like I need to tie each new thread as a handle to each forms “load” routine, but I just can’t get it to work. I don’t care about “synchronizing” threads at all, as each form is really a completely different functional program.
Any help would be much appriciated!
This isn't very common; generally it's best to limit all UI stuff to a single thread. But if you're convinced that you need each form to run on a separate thread, you must take into account the Windows API event handling model. The [over]-simplified version is that each form must have its own message loop to remove event messages from the queue and process them, so if you want to open a form on a new thread, you need to create that message pump.
The easiest way to do that is using the Application.Run method, and let the .NET Framework handle creating that message pump for you. For example:
Dim frm As Form1 = New Form1()
Application.Run(frm)
From looking at the code shown in your question, I can't discern any possible reason why those forms would need to run on separate threads. You can call the Show method of multiple forms so that they will be displayed on the screen at the same time. They won't block each other as long as you don't use the ShowDialog method, which displays each as a modal dialog. This is the way so many applications display multiple toolbox windows and other kinds of forms on the screen at the same time.
If you need to do some type of processor-intensive calculation, you still don't need to run each on a separate thread. Spin up a background thread (the BackgroundWorker class makes this very simple) and update the appropriate form's UI using the Invoke method.
You can certainly do this on Win32 but I don't know how well this maps over to .net.
The essential issue is that window handles have thread affinity. So you really need all interaction with them to happen in that thread. Essentially this means that you create all the window handles associated with that form in its thread. You also need to run a message loop in the thread.
The reason that people usually run all the UI out of the main thread and handle long-running actions in separate threads is that it is easier that way. You should ask yourself again why you want to do it this non-standard way.
I suspect you are not quite seeing the full picture. The need for threads in a desktop app principally arises when you have long running actions. Usually you want to keep your UI responsive and providing feedback for the long running action. Doing so leads to threads.
However, in your proposed solution you now have a multitude of extra threads and complexity, and you are still faced with the original problem! A long running action on one of your forms will hang it unless you perform that action in a separate thread, and once again we reach the standard solution to the problem.

Progress "bar" using threads in vb.net

I currently have a program that runs several "intense" queries. I added a textbox and presented status updates when a query was starting, eding and how many were left. This would suite my need, but the textbox doesn't actually display anything until all the queries are finished. It then displays all the updates at once. I'm assuming updating the textbox in another thread would solve this issue, and that is where I am lost. How can I use a thread that receives a message from the main form running the query and have it display it in the textbox?
The BackgroundWorker component suits your need (sample code is there in the MSDN link). You handle its DoWork event and perform the actual query in it. You report the progress by calling its ReportProgress method. To display the reported progress, you should handle its ProgressChanged event and update the UI. You start the job by calling the RunWorkerAsync method of the background worker. Using BackgroundWorker relieves you from manually starting and stopping threads and communicating with the UI thread to update the progress bar.
BackgroundWorker is a good general-purpose method for doing intensive work on a background thread. But, since your question sounds like you are doing database operations, it might be easier to use the native support for asynchronous operations in ADO.Net. You could use callbacks for the progress bar.
The easiest way to do this is making use of BackgroundWorker, handling it's DoWork event and reporting progress to the ProgressBar with ProgressChanged event.
to start:
worker.RunAsync()
report progress:
worker.ReportProgress(10) 'for 10%
Adding to what Mehrdad and Alex posted, here's how to handle the event raised by the ReportProgess method (ProgressChanged) to update the progress bar:
Private Sub backgroundWorker_ProgressChanged ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles backgroundWorker.ProgressChanged
Me.progressBar1.Value = e.ProgressPercentage
End Sub