我有一个在指定时间后引发事件的类(它在内部使用System.Timers.Timer
)。在我的测试代码中,我创建了一个Stopwatch
,它是在创建类之前启动的,并设置事件的回调以停止Stopwatch
。然后,我阻止直到Not Stopwatch.IsRunning
。简单,对吧?
我原来的阻止代码是
While Stopwatch.IsRunning
End While
但我发现像这样的空循环永远不会让我的回调发射!一旦我将调试代码放入while循环中,就可以了!:
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
是什么导致了这种奇怪的行为,更重要的是,我可以将哪些最简单的代码添加到我的阻止部分,以便触发事件?
答案 0 :(得分:9)
While Stopwatch.IsRunning
End While
它是线程中的大罪之一,被称为“热等待循环”。线程有很多罪,其中很多都没有黄带,但这一点特别阴险。主要问题是你让一个处理器核心烧得很红,在紧密循环中测试IsRunning属性。
当您使用x86抖动时,这会产生一个非常讨厌的问题,它会在发布版本中生成代码,该代码读取cpu寄存器中的IsRunning属性支持字段变量。并反复测试cpu寄存器值,而无需从字段重新加载值。这是最终的死锁,它永远不会退出循环。您通过编辑代码或使用调试器将其从该模式中删除。为避免这种情况,必须将属性的支持字段声明为 volatile ,但这不是您可以在VB.NET中执行的操作,也不是正确的修复。
相反,您应该使用正确的同步对象,该对象允许您向另一个线程发出信号。一个很好的是AutoResetEvent,你可以像这样使用它:
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
请注意,AutoResetEvent也会丢失黄色磁带。在另一个线程尚未调用WaitOne()的情况下多次调用Set()结果很差。
答案 1 :(得分:1)
不要使用Sleep或Spin。看看信号:
WaitHandle.WaitOne
阻止当前线程,直到当前WaitHandle收到 信号,使用32位有符号整数来指定时间间隔和 指定在等待之前是否退出同步域。
示例:
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!!!
在此查看更多详情:
http://msdn.microsoft.com/en-us/library/kzy257t0.aspx
答案 2 :(得分:0)
您可以尝试添加
Thread.Sleep(0)
在紧密的循环中。
答案 3 :(得分:0)
如果您确实需要主动等待,可以使用SpinWait结构。
你的空循环无法工作的原因可能是因为编译过程中的JIT看到这个循环是空的,并通过删除它来优化你的代码(只是猜测)。