如何安全地阻止.NET线程

时间:2012-11-22 21:04:12

标签: .net multithreading unit-testing blocking

我有一个在指定时间后引发事件的类(它在内部使用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

是什么导致了这种奇怪的行为,更重要的是,我可以将哪些最简单的代码添加到我的阻止部分,以便触发事件?

4 个答案:

答案 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看到这个循环是空的,并通过删除它来优化你的代码(只是猜测)。