0

我有一个在指定时间后引发事件的类(它使用System.Timers.Timer内部)。在我的测试代码中,我创建了一个Stopwatch在创建类之前启动的,并设置事件的回调来停止它Stopwatch。然后,我阻止了直到Not Stopwatch.IsRunning。很简单,对吧?

我原来的阻止代码是

While Stopwatch.IsRunning
End While

但我发现像这样的空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

是什么导致了这种奇怪的行为,更重要的是,我可以在阻止部分中放入允许事件触发的最简单代码是什么?

4

4 回答 4

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() 会很糟糕。

于 2012-11-22T21:38:00.267 回答
1

不要为此使用睡眠或旋转。研究信号:

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

于 2012-11-22T21:14:45.543 回答
0

你可以尝试添加

Thread.Sleep(0)

在你的紧密循环中。

于 2012-11-22T21:10:07.617 回答
0

如果确实需要主动等待,可以使用SpinWait结构。

你的空循环不起作用的原因可能是因为编译期间的 JIT 看到这个循环是空的,并通过删除它来优化你的代码(只是猜测)。

于 2012-11-22T21:10:42.070 回答