In VB .NET, when you call RaiseEvent X(), is the function that handles the event X processed asynchronously or synchronously. I was under the impression that RaiseEvent and the processing of the event were Synchronous unless created explictly on another thread. I've been told otherwise though.
Events are raised synchronously by default. Since MulticastDelegates are designed to support asynchronous invocation it is possible to invoke the delegates in an event's invocation list asynchronously but this is not the default behavior.
I just did some testing also...
Public Sub MyHandler() Handles Complete
MsgBox("My Handler - Beginning 5 second sleep")
Threading.Thread.Sleep(5000)
MsgBox("My Handler - Awoken")
End Sub
Public Sub SomeFunction()
MsgBox("Some function - Raising Event")
RaiseEvent Complete()
MsgBox("Some function - After Event")
End Sub
Output:
Some function - Raising Event
My Handler - Beginning 5 second sleep
My Handler - Awoken
Some function - After Event
Related
For years I create delays in my software using, for example:
Wait(10000)
Sub Wait(milliseconds)
<here I get the current time>
Do
<here I loop until the current time passed in seconds and then exit this loop>
Application.DoEvents()
Loop
End Sub
The problem is, this uses a lot of CPU. I tried Thread.Sleep(1000), but this FREEZES my application while it's performing!
I tried using a Timer, but I STILL need a loop that doesn't freeze yet acts like Application.DoEvents(). It seems impossible.
My goal is to do this:
label1.text = "ok about to start"
Wait(5000)
' the following line CAN NOT run until after 5 seconds.
label1.text = "DONE"
How to execute code after a delay.
There are different methods to execute code after a delay or execute it asynchronously, after a delay or not. Here, I'm considering a Timer and simple implementations of the Async/Await pattern.
A loop that calls Application.DoEvent() should be avoided in any case.
► Using a Timer to delay the execution of a single instruction or the code in one or more methods:
You cannot await for a Timer, but you can use a method that creates a Timer and executes an Action when the Timer raises its event, signaling that the Interval specified has elapsed.
The method that follows accept as arguments a delay value and an Action delegate.
The Delay is used to set the Timers' Interval, the Action represent the code that will be executed when the Timer Ticks (I'm using a System.Windows.Forms.Timer here, since you seem to refer to a WinForms application).
Private waitTimer As New System.Windows.Forms.Timer()
Private TimerTickHandler As EventHandler
Private Sub Wait(delay As Integer, action As Action)
waitTimer.Interval = delay
TimerTickHandler = New EventHandler(
Sub()
action.Invoke()
waitTimer.Stop()
RemoveHandler waitTimer.Tick, TimerTickHandler
End Sub)
AddHandler waitTimer.Tick, TimerTickHandler
waitTimer.Start()
End Sub
We can call this method when we need to execute code after a delay.
The Action can be a simple instruction: in this case, the Text of label1 will be set to "Done" after 5 seconds, while the UI Thread continues its operations:
label1.text = "About to Start..."
Wait(5000, Sub() Me.label1.Text = "Done")
The Action can also be a method:
Private Sub SetControlText(control As Control, controlText As String)
control.Text = controlText
End Sub
' Elsewhere
Wait(5000, Sub() SetControlText(Me.label1, "Done"))
Of course the SetControlText() method can execute more complex code and, optionally, set a Timer itself, calling Wait().
► Using the Async/Await pattern.
An async method provides a convenient way to do potentially
long-running work without blocking the caller's thread. The caller of
an async method can resume its work without waiting for the async
method to finish.
In simple terms, adding the Async modifier to a method, allows to use the Await operator to wait for an asynchronous procedure to terminate before the code that follows is executed, while the current Thread is free to continue its processing.
▬ Note that the Async modifier is always applied to a Function() that returns a Task or a Task(Of something) (something can be any value/reference a method can return).
It's only applied to a Sub() when the Sub (void) method represents an Event Handler.
This is very important to remember and apply without exceptions (unless you're quite aware of the implications). ▬
Read the Docs about this (in the previous link) and these:
Async and Await
Don't Block on Async Code
A simple Async method can be used to delay the execution of an action:
Private Async Function Wait(delay As Integer, action As Action) As Task
Await Task.Delay(delay)
action?.Invoke()
End Function
This is similar to the Timer functions and acts in a similar way. The difference is that you can Await this method to both execute the code it runs asynchronously and to wait for its completion to run other code after the method returns.
The calling Thread (the UI Thread, here), will continue its operations while the Wait() method is awaited.
Assume that, in a Button.Click handler, we want to execute an Action (a method that doesn't return a value) or a Function (a method that returns a value) and the code that follows should execute only after this Action or Function returns:
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
Await Wait(5000, New Action(Sub() Me.label1.Text = "Done"))
Await Wait(5000, Nothing)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
Here, we instruct to wait for 5 seconds, then set the Text of a Label to a value, wait other 5 seconds, doing nothing, then execute the code that follows
If we don't need to perform an Action, we can simply use Task.Delay():
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Delay(5000)
label1.Text = "Done"
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
We can also use Async/Await to wait for the completion of a Task run in a ThreadPool Thread, calling Task.Run() to execute code in a Lambda expression:
(just an example, we shouldn't use a ThreadPool Thread for such a simple task)
Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
label1.text = "About to Start..."
Await Task.Run(Async Function()
Await Task.Delay(5000)
BeginInvoke(New Action(Sub() Me.label1.Text = "Done"))
End Function)
' (...)
' ... More code here. It will execute after the last Await completes
End Sub
See also the Task.ContinueWith() method:
Creates a continuation that executes asynchronously when the target
Task completes.
During the startup of my app I am doing a long database upgrade.
Before that starts, I show a form that has a progressbar so that the user knows that something is going on and he should wait.
To not block the progressbar from redrawing, I do the database upgrade in a background worker.
The code looks like this:
frmMain_Load(...)
Dim wait As New frmWait
wait.Show()
Dim bw As New frmBWRebuildUserData
bw.Start()
Do While Not bw.Done
System.Threading.Thread.Sleep(100)
Loop
'Okay, db update was done, now continue and show the main app window
My frmBWRebuildUserData looks like this:
Public Class frmBWRebuildUserData
Private m_bDone As Boolean
Public ReadOnly Property Done() As Boolean
Get
Return m_bDone
End Get
End Property
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
modAppDB.RebuildUserDB()
End Sub
Public Sub Start()
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
m_bDone = True
End Sub
End Class
But after 60 seconds, VB.NET tells me that there were no messages since 60 seconds (I guess you know this error).
But since the background worker is intended for such purposes, I think I am doing something substantially wrong here, but I can't figure out what.
Also, my progressbar is not redrawing.
Can somebody help, please?
Thank you very much!
A couple of things.
There is no built in timeout of 60 seconds in the backgroundworker. So it should be something in your code.
Why do you use a backgroundWorker and then introduce in your code a sleep cycle? The backgroundworker should be used to free the user interface from waiting for the end of long operations.
The backgroundworker when asked to report its progress to a user interface element needs something like this (sorry is C#, but I think you can get the point)
backgroundworker.ProgressChanged += backgroundworker_ProgressChanged;
backgroundworker.WorkerReportsProgress = true;
in this scenario your modAppDB.RebuildUserDB() need to call
backgroundworker.ReportProgress(percentComplete);
for every step that you want to communicate to the progress bar and of course, you need in the form where the progressbar is displayed to intercept the event fired by the ReportProgress call
private void backgroundworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = (e.ProgressPercentage.ToString() + "%");
}
The Backgroundworker is mainly good for tasks where you have a loop inside DoWork, which allows you to do some UI feedback within the loop. If your DoWork just calls another command, it will wait for this command to finish and not do anything in this time. Other than that, using the BGW still allows for the main thread to handle its messages and not get blocked, so I presume it is still entirely right to use it here.
Apart from that, your backgroundworker1 is not declared, and as Steve pointed out, your Start()-Method needs at least this first line:
Addhandler Backgroundworker1.DoWork, AddressOf BackgroundWorker1_DoWork. This triggers the function when RunworkerAsync is called.
For a basic example of thread-communication (and fundamental problems connected with it) take a look at this question and answer:
Multithreading for a progressbar and code locations (vb.net)?
I don't know about the 60 seconds issue either.
I'm trying to invoke the below but the EventHander is not compatible with the RasConnectionEventArgs from my calling event, how would I invoke SetOverlayIcon and my notification icon on the UI thread?
Public Sub watcher_Connected(ByVal sender As Object, ByVal e As RasConnectionEventArgs)
If InvokeRequired Then
BeginInvoke(New EventHandler(AddressOf OnRegChanged))
Else
TaskbarManager.Instance.SetOverlayIcon(My.Resources.LockIcon, "Connected")
Me.NotifyIcon.ShowBalloonTip(5000, "Connected", e.Connection.EntryName, ToolTipIcon.Info)
End Sub
Jeff Winn's response to your support request:
The RasConnectionWatcher class is
multi-threaded, as such you just need
to set the SynchronizingObject
property on the component. If you have
the component on a form, you can set
it to the form instance. It will
handle the thread synchronization for
you automatically once it's been set.
Or do it similar to this:
If InvokeRequired Then
BeginInvoke(New EventHandler(Of RasConnectionEventArgs)(AddressOf watcher_Connected), sender, e)
Else
'' etc...
End If
I'm guessing at the delegate type name.
Missed the thread sync object: watcher.SynchronizingObject = Me
http://dotras.codeplex.com/Thread/View.aspx?ThreadId=232088
I've got the following code:
Public Delegate Sub SetStatusBarTextDelegate(ByVal StatusText As String)
Private Sub SetStatusBarText(ByVal StatusText As String)
If Me.InvokeRequired Then
Me.Invoke(New SetStatusBarTextDelegate(AddressOf SetStatusBarText), StatusText)
Else
Me.labelScanningProgress.Text = StatusText
End If
End Sub
The problem is that, when I call the "SetStatusBarText" sub from another thread, InvokeRequired is True (as it should be), but then my threads stall on the Me.Invoke statement - pausing execution shows them all just sitting there, not actually invoking anything.
Any thoughts about why the threads seem to be afraid of the Invoke?
The Invoke method puts a message in the message queue to perform the method call in the main thread. This means that you need a main thread with a message pump. If the main thread is busy, or if you are doing this in an application without a message pump (e.g. a console application), the message will just remain in the queue.
I have this situation: a Form with a System.Timer in it (with AutoReset = False). The form has its main thread and the timer its own thread too (nothing new here).
When the user press a button I need to stop the timer, wait until the timer thread has stopped its execution and do something more.
On the other side, the timer updates an item at the form so BeginInvoke is used. The code looks like this:
Button Code:
Private Sub ButtonStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonStop.Click
SyncLock (m_stopLock)
m_stopProcessTimer = True
Threading.Monitor.Wait(m_stopLock)
End SyncLock
''#Do more things here after the timer has end its execution and is stopped
End Sub
Timer code:
Private Sub m_processTimer_Elapsed(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_processTimer.Elapsed
Dim auxDelegate As EventHandler
SyncLock (m_stopLock)
If Not m_stopProcessTimer Then
If Me.InvokeRequired Then
auxDelegate = New EventHandler(AddressOf m_processTimer_Elapsed)
Me.BeginInvoke(auxDelegate, New Object() {sender, e})
Else
DoFormStuf()
m_processTimer.Start()
End If
Else
Threading.Monitor.Pulse(m_stopLock)
End If
End SyncLock
End Sub
The point is that I wait the main thread to let the timer thread to end its work.
The problem is that this code deadlocks when the user clicks the button when the BeginInvoke is going to be called. How a simple thing like this one can be done? Looks like I cannot find a good solution to this problem :(
Don't use locks at all, just make sure to do everything on the UI thread, and you can guarantee that nothing will be corrupted. Remember that dispatcher items run on the UI thread, so you know that if you're doing everything either in a dispatcher item or an event handler, only one thing is executing at a time.
1) Perhaps a little more code would be helpful. Do you create a new thread and put the timer ON that thread?
2) Have you tried using ManualResetEvent (.WaitOne() and .Set() instead?)
3) In your event, if invoke is required, you are re-calling your event again. Confusing...
4) Are you supposed to wait until the other thread is done? Then Thread.Join()?