Well, I guess that I'm not the first who ask this, but what is the easiest way to use threads in VB.NET? I mean, I need to download some string from a remote server and then to show that string in the GUI, so I have to use some callback function to call it in the main thread. I found different approaches for this, but all seems very difficult compared to Python where (with GTK) I used something like:
gobject.idle_add(callback_function, parameters)
and "callback_function" was executed in the main thread. How I do that in VB.NET?
Here's a simple example. It may be a little more difficult than some other languages, but it's still not terribly complicated. The following code assumes that it is within a form class (if not, you'd need to use some control or form reference to call Invoke):
Private Sub beginDoWork()
Dim thread As New Thread(AddressOf DoWork)
thread.Start()
End Sub
Public Sub DoWork()
Dim result As String = getStringFromRemoteServer()
workCompleted(result)
End Sub
Private Delegate Sub workCompletedDelegate(result As String)
Private Sub workCompleted(result As String)
If InvokeRequired Then
Invoke(New workCompletedDelegate(AddressOf workCompleted(result)
Exit Sub
End If
Label1.Text = result
End Sub
This could be further simplified by just having DoWork always call Invoke to call workCompleted rather than have workCompleted check if the invoke is required, but the way I wrote it is a bit more encapsulated and efficient if you are ever going to do the work on the UI thread instead of a worker thread.
Related
I'm new to multi-threading, so I need a general guidance how to proceed.
In a nutshell, I need to call an external webservice thousands of times per second. The response is roughly 1 second per record, which does not work well if I have a million of records to send. So, I've been tasked to use multi-threading to open multiple threads (the number is controlled dynamically) to simultaneously call WS. So, if I can open 100 threads that call WS simultaneously, the task should finish much faster... in theory.
Code is at the bottom, I've cut a lot of unnecessary pieces out of it so please let me know if something makes no sense
The idea behind this code is you would call in Threads.Process, pass DataTable with data that needs to be sent out via WS. This process would run until we processed all records in the data table and _threads (which holds a list of background objects currently working) has zero items in it.
StartThreads would instantiate new background objects if number of simultaneous threads permits and there is new data to be worked on. When a background object completes its task, in order to pass data back to the static object to log its data, it calls Threads.ThreadFinished and passes itself as parameter at which point logging info would be recorded and backgroundobject is removed from _threads.
However, during my testing I noticed that threads are overlaping when calling Threads.ThreadFinished, I tried to offset it with SyncLock to keep it thread safe, but it still does not work 100% fool proof (probably because there are other shared functions still not protected). Since I don't understand this threading very well, I have a feeling there is an easier way of doing this. So, am I on a right track? Should I keep going or switch to a different method? I've researched other multi-threading methods like ThreadPool, but I ended up with this method.
Public Class Threads
Public Shared Sub ProcessRecords(ByVal dt As DataTable)
_threads.Clear()
_startTime = Now
_dt = dt
While Working()
StartThreads()
Thread.Sleep(100)
End While
End Sub
Private Shared Sub StartThreads()
While _threads.Count < Settings.NumberOfThreads AndAlso _rowCounter < _dt.Rows.Count
Dim id As String = GetRandomChar(, 20) ' Generate a random ID for logging purposes
Dim tw As New ThreadWorker(_dt.Rows(_rowCounter), _rowCounter, id, _dt.Rows(_rowCounter)("id").ToString())
_rowCounter += 1
_threads.Add(tw)
End While
End Sub
Public Shared Sub ThreadFinished(ByVal tw As ThreadWorker)
SyncLock tw
Log(tw.Log)
_threads.Remove(tw)
End SyncLock
StartThreads()
End Sub
Private Shared Function Working() As Boolean
Return _rowCounter < _dt.Rows.Count OrElse _threads.Count > 0
End Function
End Class
Public Class ThreadWorker
Private _bw As System.ComponentModel.BackgroundWorker
Private _logDebug As New StringBuilder
Sub New(ByVal dr As DataRow)
_bw = New System.ComponentModel.BackgroundWorker
_bw.WorkerReportsProgress = True
_bw.WorkerSupportsCancellation = True
AddHandler _bw.DoWork, AddressOf bw_Working
AddHandler _bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
_bw.RunWorkerAsync()
End Sub
Private Sub bw_Working(ByVal Sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
' Call webservice ...
_logDebug.AppendLine("Response from WS")
End Sub
Public Sub bw_RunWorkerCompleted(ByVal Sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
_logDebug.AppendLine("Job completed")
Threads.ThreadFinished(Me)
End Sub
End Class
EDIT 1:
I think I've solved an issue with threads getting 'lost' - basically I have to SyncLock _threads in all Shared functions (SyncLock tw doesn't seem to do it's job). Still, this feels dirty and hacky at best. I feel like there is a better way to do things. I am currently looking into ThreadPool method, but I am a bit put off by its number of thread limits. I will potentially need to open thousands of threads.
What else I noticed is that the more concurrent threads I open, the longer it takes to get initial response from the WS. For example, if I work 1 record/thread at a time, it takes 1 second to get a response from WS. If I open 100 threads, it takes up to a minute to get a response. I suspect networking is buckling here, or maybe its a windows limitation?
Is it possible to invoke a function from a "non main" thread and then wait for it to finish before executing the rest?
I could set a boolean just before and then make the function "flip" the boolean to false when its done, but I wondered if there was a simplier/more professional way of achieving this?
Thanks
I guess you want to keep your form responsive yet you don't want to have extra procedures being called or stuff like that.
In that case the Async and Await keywords are probably a good way for you:
It is explained in detail here http://msdn.microsoft.com/en-US/en-en/library/hh191443.aspx but I will give you a short overview:
You first declare a method with the Async keyword. This can be for example the method handling a button click event in my example below.
In this method you get your result from another method that is called as a task and assign this to a temporary variable using the await keyword.
During the time the task is running the further execution of the code is halted. Your GUI stays responsive tough.
Here is a small example (just throw a Button and a Label on a Form):
Public Class Form1
''' <summary>
''' This method does the work. It is called from the async method in form of a Task(Of String).
''' </summary>
Private Function GetString() As String
'Some delay
Threading.Thread.Sleep(3000)
Return "Hello World!"
End Function
'Note the Async Keyword
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'First create the task
Dim t As Task(Of String) = New Task(Of String)(AddressOf GetString)
'Start the task
t.Start()
'Wait for the task to complete. Does not suspend your GUI!
'Much preferrable to some kind of waiting loop with DoEvents and stuff.
Dim Result As String = Await t
'Signal the end
MsgBox("DONE")
'Output the results
Label1.Text = Result
End Sub
End Class
I'll be honest, I can't really dive into the dark underbellies of how this is actually implemented in the .NET Framework since I have not read much about it myself. (I program mainly for the .NET Framework 4.0. Async/Await was introduced in 4.5. It can however be used in 4.0 as well with an extension package by Microsoft https://www.nuget.org/packages/Microsoft.Bcl.Async).
The actual usage however is not that hard as you can see, so I think this is the way to go.
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.
I'm not a VB coder but I'm tinkering with a little VB.net utility project which lets you set up a few parameters in a form and hit "go" - this then does a lot of logic which can run for several minutes.
This all happens in the go-button-handler which blocks the form. I wondered, is it easy in vb.net to make all this logic happen in a separate thread which can still update the form i.e. update a label to show which file is being processed? If it's complicated, it's not worth doing in my use-case!
Is it possible to just copy-paste my event code into a thread.Run or something like that, or even dynamically create a thread class around the code I have?
I have used the BackgroundWorker class (System.ComponentModel.BackgroundWorker) many times for things like this. It's very simple to use (compared to other multi-threading techniques available in .NET). Just drag it from the tool box onto your form, for example. If you set it's "WorkerReportsProgress" and "WorkerSupportsCancellation" properties to "True", you can even give feedback in your UI in the form of a progress bar, for example, and provide the ability for the user to click the cancel button.
Anyway, there's a lot more information about it than I can reasonably include here, so I would start by looking at this page:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
BackgroundWorker is a good choice to start. Whatever you use be aware that performance can be affected if the background thread is processor intensive i.e. a long running tight loop. This might not be very obvious if you have a multi-core CPU.
Here is a simple example of Threading.Thread.
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Button3.Enabled = False
'for example pass a string and an integer to a thread as an array
Dim params() As Object = {"one", 1} 'parameters for thread. object picked because of mixed type
Dim t As New Threading.Thread(AddressOf someThread)
t.IsBackground = True
t.Start(params) 'start thread with params
End Sub
Public Sub someThread(params As Object)
'not on the UI
Dim theparams() As Object = DirectCast(params, Object()) 'convert object to what it really is, an array of objects
Dim param1 As String = DirectCast(theparams(0), String)
Dim param2 As Integer = DirectCast(theparams(1), Integer)
Debug.WriteLine(param1)
Debug.WriteLine(param2)
showOnUI(param1)
End Sub
Public Sub showOnUI(s As String)
If Me.InvokeRequired Then
'not running on UI
Me.Invoke(Sub() showOnUI(s)) 'run method on UI
Else
'running on UI
Label1.Text = s
Button3.Enabled = True
End If
End Sub
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.