Recently, I've been working with a system.form.timer in the UI thread. I've have noticed that while I can stop a timer on a background thread, I cannot start it back up unless I use BeginInvoke even though I do not receive a cross-threading exception. On a system.timers.timer however it seems that I can stop and start it from the background thread created by the timer. Why is this? Is a system.form.timer allowed to be stopped, but not enabled from a background thread? This seems a bit odd to me.
System.Form.Timer Code
DOESN'T WORK
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim BW As BackgroundWorker = New BackgroundWorker
AddHandler BW.DoWork, AddressOf CheckTimer
BW.RunWorkerAsync()
End Sub
Private Sub CheckTimer()
Timer1.Stop()
Timer1.Start()
MsgBox("Stopped and Started Timer")
End Sub
WORKS
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim BW As BackgroundWorker = New BackgroundWorker
AddHandler BW.DoWork, AddressOf CheckTimer
BW.RunWorkerAsync()
End Sub
Private Sub CheckTimer()
Timer1.Stop()
Me.BeginInvoke(New TimerStart(AddressOf TimerStartFunction))
MsgBox("Stopped and Started Timer")
End Sub
Private Delegate Sub TimerStart()
Private Sub TimerStartFunction()
Timer1.Start()
End Sub
System.Timers.Timer Code
WORKS
Dim aTimer As System.Timers.Timer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
aTimer = New System.Timers.Timer(5000)
AddHandler aTimer.Elapsed, AddressOf OnTimedEvent
aTimer.Enabled = True
End Sub
Sub OnTimedEvent()
aTimer.Stop()
aTimer.Start()
MsgBox("Stopped and Started Timer")
End Sub
The Winforms Timer class is somewhat thread-safe, not enough to keep you happy. When you call its Start() method then it creates a hidden window that turns WM_TIMER messages into Tick events.
When you do this on a worker thread then you tend to have a problem, these WM_TIMER messages are only dispatched when the thread runs a message loop. Worker threads don't normally do this, they don't call Application.Run(). So the timer just won't tick.
Calling the Stop() method is otherwise okay, it knows how to find that hidden window back even through the code runs on the wrong thread. The workaround you found with BeginInvoke() works because it now correctly calls Start() on the UI thread and gets the hidden window created with the correct owner thread, one that pumps. System.Timers.Timer doesn't have this problem, it doesn't rely on WM_TIMER to tick but uses a System.Threading.Timer instead. Which is supported by a dedicated worker thread managed by the CLR. Do note that this timer is pretty dangerous. Calling MsgBox() on a worker thread is fundamentally wrong, for one. High odds that the user never sees it since it will be behind a UI window.
That explains it, I can't otherwise offer better advice since I can't see what you are really trying to do. Be careful, you are playing with fire.
Related
My class watches a directory for incoming files. It does do so with a FileSystemWatcher object, only monitoring the FSW's Created events.
On a Created event, I start a potentially time-consuming process (file-deserialization is needed, sending an event to the client using my class, in which all sorts of things might happen). Thus, I start a BackgroundWorker object to do all this work, ultimately culminating in the received file's removal.
However, during all this work, new files may appear. In the Created event I check, if the BGW is still busy, and if so, I just store the fully qualified name in a queue for later consumption.
Public Sub New(Path As String)
FSM = New FileSystemWatcher
With FSW
.Path = Path
AddHandler .Created, AddressOf pFileArrived
End With
BGW = New BackgroundWorker
With BGW
.WorkerReportsProgress = False
.WorkerSupportsCancellation = False
AddHandler .DoWork, AddressOf BGW_DoWork
AddHandler .RunWorkerCompleted,
AddressOf BGW_RunWorkerCompleted
End With
End Sub
Private Sub pFileArrived(sender As Object, e As FileSystemEventArgs)
pNotifyClient(e.FullPath)
End Sub
Private Sub pNotifyClient(sFullPath As String)
If Not BGW.IsBusy Then
BGW.RunWorkerAsync(sFullPath)
Else
MyQueue.Enqueue(sFullPath)
End If
End Sub
Private Sub BGW_DoWork(ByVal sender As Object,
ByVal e As DoWorkEventArgs)
'...
End Sub
But how can I find out, when the BGW is done?
I know, that there is the RunWorkerCompleted event. However, this event is fired from a real BGW instance still existing, so I can not go on and simply call it again from within the event handler.
Private Sub BGW_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs)
'This won't work.
If MyQueue.Count > 0 Then
BGW.RunWorkerAsync(MyQueue.Dequeue)
End If
End Sub
What is the proper way of doing such things? Initializing a timer does spring to mind, but it doesn't seem right. (How much time should I give it? Should I loop for the BGW thread's end?)
Or should I consider another approach than invoking a BGW?
If you want to nuke this problem from the orbit, put an AutoResetEvent(false) somewhere and your BGW's last task should be to evt.Set() and your main thread can do evt.WaitOne(0) to just query the status. If there's a possibility of running multiple BGWs at the same time, you need some data structure to keep track of which ARE is associated with which BGW.
A larger investment would be switch to pure producer-consumer design, which involves a queue (which you already have in a form) and consumer thread(s) to dequeue work items and process them.
I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.
When I start the timer during a process it freezes my program. Is there any way to resolve it? To make it not freeze all buttons in the GUI while the timer is working?
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Do somting...(I sending mail throught SMTP)
End Sub
This has nothing to do with the timer.
You're running a long (network-bound) operation on the UI thread.
Whenever code is running on the UI thread, the UI cannot respond.
You need to run that operation asynchronously or on a background thread.
Incase you still don't understand Slacks answer ...
instantiate the thread
Public t1 As Threading.Thread
make a call from your timer to the thread.
Private Sub someTimer(sender As Object, e As EventArgs) Handles someTimer.Tick
t1 = New Thread(New ThreadStart(AddressOf SomeSubRoutine))
t1.Start()
end sub
Run email code in the subroutine
sub Subroutine()
email code here // make sure that therer are no GUI or Main Thread calls else you have to get into delegates and invoke methods
end sub
your done, won't hang up the Gui Thread, however i do recoment staying away from timers I would call the thread directly
You can also try to use Application.DoEvents() inside your loop in Timer1_Tick function if you have a for-loop or while-loop inside the function.
I'm seeing some strange behavior where the RunWorkerCompleted event for one of two threads I start isn't being called depending on how I call them. Check out the code below, and the two methods of firing the threads, good() and bad().
Public Class Form1
Private WithEvents bw As System.ComponentModel.BackgroundWorker
Private WithEvents bw2 As System.ComponentModel.BackgroundWorker
Private starts As Integer = 0
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
starts += 1
Threading.Thread.Sleep(e.Argument)
End Sub
Private Sub bw_Completed(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
MessageBox.Show("Ending " + starts.ToString())
End Sub
Private Sub bad()
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5000)
Threading.Thread.Sleep(500)
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5)
End Sub
Private Sub good()
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(500)
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(5)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'good()
bad()
End Sub
End Class
In both cases the DoWork event is called for both threads. But in the bad() case, only the second thread fires the RunWorkerCompleted event. This is obviously due to the two different ways I'm using VB to handle events here. I'm looking for an explanation of this behavior, preferably with a link to some documentation that could help me understand how these events are being handled in VB better. It seems strange to me that just reusing a variable name here seems to either dispose of the thread before it's done or else just make it stop firing events.
Where is this automatic unsubscribing documented?
In the Visual Basic Language Specification, a document you can download from Microsoft. Chapter 9.6.2 "WithEvents Variables" says this:
The implicit property created by a WithEvents declaration takes care of hooking and unhooking the relevant event handlers. When a value is assigned to the variable, the property first calls the remove method for the event on the instance currently in the variable (unhooking the existing event handler, if any). Next the assignment is made, and the property calls the add method for the event on the new instance in the variable (hooking up the new event handler).
The bolded phrase describes the behavior you see. It is rather important it works that way. If it didn't then you could never unsubscribe from an event and the event subscriptions would pile on without limit.
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()?