0

I have a class that raises an event after a specified time (it uses a System.Timers.Timer inside). In my test code, I created a Stopwatch which I started before the class was created and set the callback for the event to stop that Stopwatch. Then, I blocked until Not Stopwatch.IsRunning. Simple, right?

My original blocking code was

While Stopwatch.IsRunning End While 

but I found that having an empty while loop like that never allowed my callback to fire! As soon as I put debugging code into the while loop, it worked!:

Dim lastSecond As Integer = 0 While sw.IsRunning If (Date.Now.Second > lastSecond) Then lastSecond = Date.Now.Second Console.WriteLine("blocking...") End If End While 

What causes this strange behavior, and more importantly, what's the simplest code I can put into my blocking section that will allow the event to fire?

3
  • what version of .NET are you using? Commented Nov 22, 2012 at 21:12
  • 4.0 but I could switch to 4.5 relatively easily Commented Nov 22, 2012 at 21:22
  • then don't forget about Tasks, and in particular await Task.Yield() which allow the message pump of the UI thread to run when called on the UI Thread, or await Task.Delay(delay) which will pause the execution without keeping the current thread blocked like Sleep. I won't elaborate, as the real answer to your question is really Hans one. Commented Nov 23, 2012 at 9:43

4 Answers 4

9
While Stopwatch.IsRunning End While 

It is one of the Great Sins in threading, called a "hot wait loop". Threading has many sins, and many of them have no yellow tape at all, but this one is particularly insidious. The principal problem is that you keep one processor core burning red hot, testing the IsRunning property in a tight loop.

This begets a very nasty problem when you use the x86 jitter, it generates code in the release build that reads the IsRunning property backing field variable in a cpu register. And tests the cpu register value over and over again, without reloading the value from the field. That's the ultimate deadlock, it can never exit the loop. You bumped it out of that mode by editing the code or by using the debugger. To avoid it, the backing field of the property must be declared volatile but that's not something you can do in VB.NET, nor is it the proper fix.

Instead you should use a proper synchronization object, one that lets you signal another thread that something happened. A good one is the AutoResetEvent, you'd use it like this:

Dim IsCompleted As New AutoResetEvent(False) Private Sub WaitForTimer() IsCompleted.WaitOne() ''etc.. End Sub Private Sub timer_Elapsed(ByVal sender As Object, ByVal e As EventArgs) Handles timer.Elapsed IsCompleted.Set() timer.Stop() End Sub 

Beware that AutoResetEvent has yellow tape missing as well. Calling Set() more than once while the other thread hasn't yet called WaitOne() ends up poorly.

Sign up to request clarification or add additional context in comments.

2 Comments

Could you clarify what you mean by "yellow tape"?
Ever seen an American crime show on TV like Law & Order? The scene where the crime was committed is cordoned-off by yellow tape to prevent anybody from blundering in and destroy the evidence. In other words: "pay attention, something unusual is going on here!"
1

Don't use Sleep or Spin for this. look into signalling:

WaitHandle.WaitOne 

Blocks the current thread until the current WaitHandle receives a signal, using a 32-bit signed integer to specify the time interval and specifying whether to exit the synchronization domain before the wait.

Example:

Imports System Imports System.Threading Imports System.Runtime.Remoting.Contexts <Synchronization(true)> Public Class SyncingClass Inherits ContextBoundObject Private waitHandle As EventWaitHandle Public Sub New() waitHandle = New EventWaitHandle(false, EventResetMode.ManualReset) End Sub Public Sub Signal() Console.WriteLine("Thread[{0:d4}]: Signalling...", Thread.CurrentThread.GetHashCode()) waitHandle.Set() End Sub Public Sub DoWait(leaveContext As Boolean) Dim signalled As Boolean waitHandle.Reset() Console.WriteLine("Thread[{0:d4}]: Waiting...", Thread.CurrentThread.GetHashCode()) signalled = waitHandle.WaitOne(3000, leaveContext) If signalled Then Console.WriteLine("Thread[{0:d4}]: Wait released!!!", Thread.CurrentThread.GetHashCode()) Else Console.WriteLine("Thread[{0:d4}]: Wait timeout!!!", Thread.CurrentThread.GetHashCode()) End If End Sub End Class Public Class TestSyncDomainWait Public Shared Sub Main() Dim syncClass As New SyncingClass() Dim runWaiter As Thread Console.WriteLine(vbNewLine + "Wait and signal INSIDE synchronization domain:" + vbNewLine) runWaiter = New Thread(AddressOf RunWaitKeepContext) runWaiter.Start(syncClass) Thread.Sleep(1000) Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode()) ' This call to Signal will block until the timeout in DoWait expires. syncClass.Signal() runWaiter.Join() Console.WriteLine(vbNewLine + "Wait and signal OUTSIDE synchronization domain:" + vbNewLine) runWaiter = New Thread(AddressOf RunWaitLeaveContext) runWaiter.Start(syncClass) Thread.Sleep(1000) Console.WriteLine("Thread[{0:d4}]: Signal...", Thread.CurrentThread.GetHashCode()) ' This call to Signal is unblocked and will set the wait handle to ' release the waiting thread. syncClass.Signal() runWaiter.Join() End Sub Public Shared Sub RunWaitKeepContext(parm As Object) Dim syncClass As SyncingClass = CType(parm, SyncingClass) syncClass.DoWait(False) End Sub Public Shared Sub RunWaitLeaveContext(parm As Object) Dim syncClass As SyncingClass = CType(parm, SyncingClass) syncClass.DoWait(True) End Sub End Class ' The output for the example program will be similar to the following: ' ' Wait and signal INSIDE synchronization domain: ' ' Thread[0004]: Waiting... ' Thread[0001]: Signal... ' Thread[0004]: Wait timeout!!! ' Thread[0001]: Signalling... ' ' Wait and signal OUTSIDE synchronization domain: ' ' Thread[0006]: Waiting... ' Thread[0001]: Signal... ' Thread[0001]: Signalling... ' Thread[0006]: Wait released!!! 

See more details here:
http://msdn.microsoft.com/en-us/library/kzy257t0.aspx

Comments

0

You could try adding

Thread.Sleep(0) 

in your tight loop.

3 Comments

That works, but... If it's sleeping for 0, what would that actually accomplish?
Your empty loop probably got compiled out, so it never got executed, the sleep for 0 will (should) stay compiled in but shouldn't cost you any sleep time
Sleep(0) will give up the rest of the time-slice given to your thread, allowing other threads to do work. You cold also sleep for a short time.
0

If you really need active waiting, you can use SpinWait structure.

And reason why your empty loop didn't work could be because JIT during compilation saw that this loop is empty, and optimized your code by removing it (just guessing).

2 Comments

The program hangs indefinitely when the loop is empty, so it's definitely not optimized out.
My apologies, i misunderstood your code behavior then. Other explanation might be, that Console.Writeline performs I/O operation which allows the second (timer) thread to do it works. If your main thread (one where) you're making your while loop has higher priority then Timer's thread, and it doesn't give him any time slices to execute the callback? But probably using approach that @Abdias Software suggested rather then SpinWait or Thread.Sleep would be the best.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.