How to make a loader in a separate thread? - vb.net

I have a main form wich is expected to perfom some long operations. In parallel, I'm trying to display the percentage of the executed actions.
So I created a second form like this:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
My main sub, the one which is expected to perform the long operations is in another form as follows:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
The code works almost 95% of the time but sometimes I'm getting a thread exception somewhere in the sub DisplayPercentage. I change absolutely nothing, I just hit the start button again and the debugger continues the execution without any problem.
The exception I get is: Cross-thread operation not valid: Control 'LoaderClass' accessed from a thread other than the thread it was created on event though I'm using : if InvokeRequired
Does anyone know what is wrong with that code please ?
Thank you.

This is a standard threading bug, called a "race condition". The fundamental problem with your code is that the InvokeRequired property can only be accurate after the native window for the dialog is created. The problem is that you don't wait for that. The thread you started needs time to create the dialog. It blows up when InvokeRequired still returns false but a fraction of a second later the window is created and Invoke() now objects loudly against being called on a worker thread.
This requires interlocking, you must use an AutoResetEvent. Call its Set() method in the Load event handler for the dialog. Call its WaitOne() method in InitApplication().
This is not the only problem with this code. Your dialog also doesn't have a Z-order relationship with the rest of the windows in your app. Non-zero odds that it will show behind another window.
And an especially nasty kind of problem caused by the SystemEvents class. Which needs to fire events on the UI thread. It doesn't know what thread is the UI thread, it guesses that the first one that subscribes an event is that UI thread. That turns out very poorly if that's your dialog when it uses, say, a ProgressBar. Which uses SystemEvents to know when to repaint itself. Your program will crash and burn long after the dialog is closed when one of the SystemEvents now is raised on the wrong thread.
Scared you enough? Don't do it. Only display UI on the UI thread, only execute slow non-UI code on worker threads.

Thank you for your proposal. How to do that please ? Where should I
add Invoke ?
Assuming you've opted to leave the "loading" code of the main form in the main UI thread (probably called from the Load() event), AND you've set LoaderClass() as the "Splash screen" in Project --> Properties...
Here is what LoaderClass() would look like:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*This is the same as what you had but I moved the delegate into the class.
*Note that you do NOT need the CloseForm() method as the framework will automatically close your splash screen once the main form is completely loaded.
Now, over in the main form, you can grab the displayed instance of the splash screen with My.Application.SplashScreen and cast it back to LoaderClass(). Then simply call your DisplayPercentage() method at the appropriate times with appropriate values:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
If all goes well, your splash screen should automatically display, update with progress, then automatically close when your main form has finished loading and displayed itself.

Me.Invoke(TempFunction, Value)
Should be:
Me.Invoke(TempFunction, new Object(){Value})
because the overload with parameters takes an array of parameters.
Value is on the stack of the function in the current thread. You need to allocate memory on the GC heap and copy the value to that memory so that it is available to the other thread even after the local stack has been destroyed.

Related

Accessing UI thread controls from 2 joining multi thread

I'm currently working on a small auto-update project for my company. After some research on multi-threading, I manage to built up the code below :
Thread #01 :
Private Sub startUpdate()
If InvokeRequired Then
Invoke(New FTPDelegate(AddressOf startUpdate))
Else
'some code here
End If
End Sub
Thread #02 which is joined by thread #01 :
Private Sub startProcess()
myThread = New Thread(Sub() startUpdate())
myThread.Start()
myThread.Join()
'another code goes here
Me.close
End Sub
And thread #02 is accessed when the form loads :
Private Sub SUpdater_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
myThread1 = New Thread(Sub() startProcess())
myThread1.Start()
End Sub
There are 2 things which I'm stuck with :
I can't access Me.close from thread #01. It fires an error:
Control is in another thread
The main form froze even though I called another thread.
Please help me fix this error.
Thank you very much.
Invocation is required every time you are to access UI elements. Calling Me.Close() starts to dispose all the form's elements (components, buttons, labels, textboxes, etc.), causing interaction with both the form itself, but also everything in it.
The only things you are not required to invoke for are properties that you know doesn't modify anything on the UI when get or set, and also fields (aka variables).
This, for example, would not need to be invoked:
Dim x As Integer = 3
Private Sub Thread1()
x += 8
End Sub
To fix your problem you just need to invoke the closing of the form. This can be done simply using a delegate.
Delegate Sub CloseDelegate()
Private Sub Thread1()
If Me.InvokeRequired = True Then 'Always check this property, if invocation is not required there's no meaning doing so.
Me.Invoke(New CloseDelegate(AddressOf Me.Close))
Else
Me.Close() 'If invocation is not required.
End If
End Sub

Cross Thread Error Trying To Open New Form Instance Inside Timer

I am trying to create little notification popups for my application and have created a new form that fades in and out and sits on top of my main form (seems to work okay).
My problem is that I have some code that sits inside a timer event that does some data checking every minute or so. Depending on the data results, I sometimes need to show a notification. However, it is causing me Cross-Thread errors (which is understandable), but I'm not sure how to get around it.
Example (in a nutshell) of what I am trying to do is:
Private Sub RefreshData(sender As Object, e As System.Timers.ElapsedEventArgs)
Try
MainRefreshTimer.Interval = GetInterval()
MainRefreshTimer.Start()
'Do some data checking here...
If data returns true then
Dim notify as New frmNewNotification("Some Text", 10) '<== Show some text for 10 seconds then close the form automatically
notify.Show() '<== Cross Thread Error occurs from this
End If
...
End Sub
I would try one of this ideas:
Shorcut: Put this in your Form_Load
Control.CheckForIllegalCrossThreadCalls = False
Or, better, something like:
Private Sub delRefreshData(data as Object)
If Me.InvokeRequired Then
' Invoke(New MethodInvoker(AddressOf delRefreshData)) ' no params
Invoke(New MethodInvoker(Sub() delRefreshData(data)))
Else
'Do some data checking here...
If data returns true then
Dim notify as New frmNewNotification("Some Text", 10)
notify.Show() '
End If
End if
Using InvokeRequired vs control.InvokeRequired
Edited:
To avoid in future be blamed for that Shorcut, I have to say that
Control.CheckForIllegalCrossThreadCalls isn't a good advice/solution, as is discussed here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on

vb.net problems with invoke

I have a thread that runs background jobs and is required to update the GUI once in a while. My program has been designed so that when the user clicks off of a form, the thread and background operations still run, yet the controls have been disposed (for memory management purposes).
I have been using Invoke() and "If Control.Created = True" to make sure that the thread can successfully update the controls without running into any exceptions. However, when the form is recreated, all "Control.Created" values are false and Invoke() fails with "{"Invoke or BeginInvoke cannot be called on a control until the window handle has been created."}"
My guess is that this has something to do with the fact that when the form is recreated it is assigned different handles and that the "Invoke()" is looking at the old handle. SO my question is, how do I fix this?
EDIT: As per requested, the code for opening the form and where the bg thread works from
Opening the DropLogMDIalt form is simply
FormCTRL.Show()
The Background Thread runs when the control is modified so that the NumericUpDown is more than 0 (so that there is something to countdown from)
Private Sub NLauncherTerminateInput_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DLScanInterval.ValueChanged
If DLScanInterval.Created = True Then
DLTimerControlValue = DLScanInterval.Value
If DLTimerControlValue = 0 Then
CancelDropLogTimer()
Else
If DLScanIntervalControl.Active = False Then
BeginDropLogTimer()
End If
End If
End If
End Sub
Public Sub BeginDropLogTimer()
Dim N As New Threading.Thread(AddressOf DropLogTimerIntervalThreadWorker)
N.Start()
DLScanIntervalControl.ThreadID = N.ManagedThreadId
DLScanIntervalControl.Active = True
End Sub
Public Sub CancelDropLogTimer()
DLScanIntervalControl.Active = False
End Sub
Public Sub DropLogTimerIntervalThreadWorker()
DLScanTimerSecondsLeft = DLTimerControlValue * 60
Dim s As Integer = DLTimerControlValue
Do Until 1 = 2
DLScanTimerSecondsLeft = DLTimerControlValue * 60
Do Until DLScanTimerSecondsLeft <= 0
If Not (DLTimerControlValue = 0 Or DLScanIntervalControl.CancelPending = True) Then
Else
Exit Sub
End If
If Not DLTimerControlValue = s Then
DLScanTimerSecondsLeft = DLTimerControlValue * 60
s = DLTimerControlValue
End If
Dim ToInvoke As New MethodInvoker(Sub()
Timer(DLScanTimerSecondsLeft, ":", DLScanIntervalTB)
End Sub)
If (Me.IsHandleCreated) Then
If (InvokeRequired) Then
Invoke(ToInvoke)
Else
ToInvoke()
End If
End If
Threading.Thread.Sleep(1000)
DLScanTimerSecondsLeft -= 1
Loop
CompareScan = True
PerformScan()
Loop
End Sub
The thread is simply called by declaring a new thread.thread, however, I have created a class and a variable that the thread uses to check if it should still be running or not (similarly to how a backgroundworker would) this is illustrated by the "DLScanIntervalControl.CancelPending"
The form is then closed later by
Form.Close()
It can be reopened if the user clicks on a label that then uses the same method as shown above (FormCTRL.Show())
From MSDN:
"If the control's handle does not yet exist, InvokeRequired searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, the InvokeRequired method returns false."
In other words, you need to verify that the handle is created and then check if invoke is required.
If(Me.IsHandleCreated) Then
If(Me.InvokeRequired) Then
'...
Else
'...
End If
End If
I had a similar error when trying to use delegates to update controls on a form in another thread. I found that the handle is only created when it's "needed". I'm not sure what constitutes "needed", but you can force it to create the handle by accessing the Handle property of the object.
What I had done in my application is this:
' Iterate through each control on the form, and if the handle isn't created yet, call the
' Handle property to force it to be created
For Each ctrl As Control In Me.Controls
While Not ctrl.IsHandleCreated
Dim tmp = ctrl.Handle
tmp = Nothing
End While ' Not ctrl.IsHandleCreated
Next ' ctrl As Control In Me.Controls
It's rather ghetto, but it may help you here (If you still need the help)
I think the issue here has nothing to do with invoking, but references. Following this pseudocode...
Dim A As New Form1
A.Show()
''Spawn background thread with a reference to A
A.Dispose()
Dim B As New Form1
B.Show()
The thread is attempting to refer to the first instance of Form1 above which is disposed and will always stay that way.
If you want the thread to be able to update any form then you need to give the thread a (synchronised) way to refer to the form...
Public Class Worker
Private Target As Form1
Private TargetLock As New Object
Public Sub SetTargetForm(Frm as Form1)
SyncLock TargetLock
Target = Frm
End SyncLock
End Sub
Public Sub DoWork() ''The worker thread method
''Do work as usual then...
SyncLock TargetLock
If Target IsNot Nothing AndAlso Target.IsHandleCreated Then
If Target.InvokeRequired
Target.Invoke(...)
Else
...
End If
End If
End SyncLock
End Sub
End Class
This way, when a new form is available, you can inform the worker thread using SetTargetForm() and it will update the appropriate one.
Of course, you'd be better off refactoring the "Update UI" checks and invoke calls into a different method for simplicity/maintainability but you get the point.
Note that I haven't got an IDE to hand so there may be typos.
One final point... I'd question the value of disposing a form for memory management purposes. Controls are fairly lightweight in terms of memory and it's far more common for an object used by the form to be a memory hog than the form itself. Are you sure you're getting a real benefit for this added complexity?

Why doesn't this multithreaded VB.NET (2010 Express Edition) program work properly?

I'm trying to make a program of mine into a multithreaded application, but I've hit a pair of snags that I documented in the following code. Any help that I can get with this to make it behave properly would be greatly appreciated so I can expand this stub into a more efficient version of my existing application.
Thank you for any advice you have on this matter.
- Aaron
Imports System.Threading
Public Class frmMain
''' <summary>Initializes the multithreaded form</summary>
Private Sub Initialize() Handles MyBase.Load
AddThread(AddressOf Update_UI)
running = True
For Each Thread In ThreadPool
Thread.IsBackground = True
Thread.Start()
Next
End Sub
''' <summary>Terminates the multithreaded form</summary>
Protected Overrides Sub Finalize() Handles MyBase.FormClosing
running = False
For Each Thread In ThreadPool
Thread.Join()
Thread = Nothing
Next
End Sub
''' <summary>Adds a worker thread to the ThreadPool</summary>
''' <param name="pointer">The AddressOf the function to run on a new thread.</param>
Private Sub AddThread(ByRef pointer As System.Threading.ParameterizedThreadStart)
Dim newthread As Integer
If ThreadPool Is Nothing Then newthread = 0 Else newthread = ThreadPool.GetUpperBound(0) + 1
ReDim Preserve ThreadPool(newthread)
ThreadPool(newthread) = New Thread(pointer)
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub Update_UI()
'HELP: The commented out lines in this subroutine make the program work incorrectly when uncommented.
'HELP: It should echo 'output' to the titlebar of frmMain, but it also makes the form unresponsive.
'HELP: When I force the form to quit, the 'termination alert' does not trigger, instead the application hangs completely on Thread.Join (see above).
'HELP: If I remove DoEvents(), the form is unable to be closed...it simply goes unresponsive. Shouldn't the multithreading keep us from needing DoEvents()?
'If Me.InvokeRequired Then
' Me.Invoke(New MethodInvoker(AddressOf Update_UI))
'Else
While running
Dim output As String = System.DateTime.Now + " :: Update_UI() is active!"
Debug.Print(output)
'Application.DoEvents()
'Me.Text = output
End While
Debug.Print(System.DateTime.Now + " :: Termination signal recieved...")
'End If
End Sub
Delegate Sub dlgUpdate_UI()
Private ThreadPool() As Thread
Private running As Boolean
End Class
Yes, none of what you tried can work properly. You correctly identified the need to use Control.Invoke() to run the Me.Text assignment on the main thread. This is what is going wrong:
Your Invoke() call makes the entire method run on the main thread. It will start executing the loop and never exit. Your form goes catatonic since it can't do anything else anymore, like repaint the caption bar to show the changed text or respond to user input
The DoEvents call makes the form come back alive but now you've got a new problem: the user can close the window and your code keeps running. The running flag will never be set to false so the program won't stop. The user interface is gone though. Code would normally bomb on an ObjectDisposedException but not in your specific case, the Text property is stored in a private variable
You could alter the code so that only the Me.Text assignment runs on the main thread. But now you've got a new problem: the main thread will get pummeled by invoke request and doesn't get around to doing its regular (low priority) duties anymore. It goes catatonic. The essential problem is that you are trying to update the caption bar way too fast. There's no point, the user cannot read that fast. Update 20 times per second is plenty and looks smooth to the human eye
Do not use the Finalize() method for tasks like this, the code can easily trigger the 2 second finalizer thread time-out, bombing your program.
Do consider using the BackgroundWorker class, it takes care of some of these details.
It's the do while loop that is burning up all your cycles so you are loosing the battle and keeping the procesor busy no matter how many threads you use. Something like the following will be better suited for what you are trying to achieve.
Imports System.Threading
Public Class Form1
Private t As New Timer(AddressOf DoTimer, Nothing, 1000, 1000)
Private Sub DoTimer(ByVal state As Object)
UpdateUi()
End Sub
''' <summary>Updates the User Interface</summary>
Private Sub UpdateUi()
If InvokeRequired Then
Invoke(New DlgUpdateUi(AddressOf UpdateUi))
Else
Dim output As String = DateTime.Now & " :: Update_UI() is active!"
Debug.Print(output)
Text = output
End If
End Sub
Delegate Sub DlgUpdateUi()
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
t.Dispose()
End Sub
End Class
If I have said once I have said it a million times. Using Invoke, while useful in many situations, is abused and way overused. If all you want to do is have the progress of the worker threads displayed to a user then using Invoke is not always the best option. And it does not look like the best option here either.
Instead, publish the status text you are assigning to output into a variable that can be accessed via the UI thread. Then use a System.Windows.Forms.Timer to periodically poll its value at a more reasonable rate...maybe every 1 second or so. The Tick event already runs on the UI thread so you can immediately begin using this value to display to the end user by manipulating the various UI controls.
Strings are really easy to pass around from thread to thread because they are immutable which means they are inherently thread-safe. The only thing you really have to worry about is making sure the UI thread sees the most recent reference published to the shared variable. In C# you would use the volatile keyword for this. In VB you can use Thread.VolatileRead from the UI thread and Thread.VolatileWrite from the worker thread. Of course, if you are more comfortable wrapping the reads and writes in a SyncLock that is perfectly acceptable as well.

vb.net - background thread issue

For some reason a background thread in my app can't change any labels, textbox values, etc on my main form. There is no compile errors, when the thread executes nothing happens.
Here is some example code:
Imports System.Threading
Public Class Class1
Dim tmpThread As System.Threading.Thread
Private Sub bgFindThread()
Form1.lblStatus.Text = "test"
End Sub
Public Sub ThreadAction(ByVal Action As String)
If Action = "Start" Then
tmpThread = New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf bgFindThread))
tmpThread.Start()
ElseIf Action = "Abort" Then
If tmpThread.IsAlive = True Then tmpThread.Abort()
End If
End Sub
End Class
Can someone let me know what I'm doing wrong?
AFAIK code above will throw an exception IllegalCrossThreadException, it is because the background thread is not the same as UI thread and background try to set value on other thread. So windows form check every thread that work properly.
You can set Control.CheckForIllegalCrossThreadCalls to false to make it works.
Code below is when setting property is not run
Add into your code
------------------------------
Delegate Sub MyDelegate()
Private Sub RunMyControl()
lblStatus.Text = "test"
End Sub
Change your code
------------------------------
Private Sub bgFindThread
lblStatus.BeginInvoke (New MyDelegate(AddressOf RunMyControl))
End Sub
The method asyncronsly run code from background thread to UI thread.
You can only access UI controls from the UI thread.
I suggest reading this first: http://www.albahari.com/threading/
As others have mentioned, it is forbidden (for good reasons) to update UI elements from a non-UI thread.
The canonical solution is as follows:
Test whether you are outside the UI thread
If so, request for an operation to be performed inside the UI thread
[Inside the UI thread] Update the control.
In your case:
Private Sub bgFindThread()
If lblStatus.InvokeRequired Then
lblStatus.Invoke(New Action(AddressOf bgFindThread))
Return
End If
lblStatus.Text = "test"
End Sub
The only thing that changed is the guard clause at the beginning of the method which test whether we’re inside the UI thread and, if not, requests an execution in the UI thread and returns.
You can use a delegate to update UI controls in a background thread.
Example
Private Delegate Sub bkgChangeControl(ByVal bSucceed As Boolean)
Private dlgChangeControl As bkgChangeControl = AddressOf ChangeControl
Private Sub threadWorker_ChangeControl(ByVal bSucceed As Boolean)
Me.Invoke(dlgChangeControl, New Object() {bSucceed})
End Sub
Private Sub ChangeControl()
Me.lable="Changed"
End Sub
'In your background thread, call threadWorker_ChangeControl.