如何安全地阻止.NET线程

如何安全地阻止.NET线程,.net,multithreading,unit-testing,blocking,.net,Multithreading,Unit Testing,Blocking,我有一个类在指定的时间后引发事件(它在内部使用System.Timers.Timer)。在我的测试代码中,我创建了一个Stopwatch,我在创建类之前启动了它,并将事件的回调设置为停止该Stopwatch。然后,我阻塞直到没有秒表。正在运行。很简单,对吧 我最初的阻塞代码是 秒表运行时 结束时 但我发现这样一个空的while循环永远不会让我的回调触发!我一把调试代码放入while循环,它就工作了!: Dim lastssecond作为整数=0 当软件运行时 如果(Date.Now.Secon

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

我最初的阻塞代码是

秒表运行时

结束时
但我发现这样一个空的while循环永远不会让我的回调触发!我一把调试代码放入while循环,它就工作了!:

Dim lastssecond作为整数=0
当软件运行时
如果(Date.Now.Second>lastsond),则
lastSecond=Date.Now.Second
Console.WriteLine(“阻塞…”)
如果结束
结束时
是什么导致了这种奇怪的行为,更重要的是,我可以将什么最简单的代码放在我的阻止部分,以允许触发事件?

您可以尝试添加

Thread.Sleep(0)

如果你真的需要主动等待,你可以使用结构


空循环不起作用的原因可能是因为JIT在编译过程中发现这个循环是空的,并通过删除它来优化代码(只是猜测)。

不要为此使用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!!!
导入系统
导入系统线程
导入System.Runtime.Remoting.Context
公共类同步类
继承ContextBoundObject
作为EventWaitHandle的私有waitHandle
公共分新()
waitHandle=新的EventWaitHandle(错误,EventResetMode.ManualReset)
端接头
公共副信号机()
Console.WriteLine(“线程[{0:d4}]:信令…”,Thread.CurrentThread.GetHashCode()
waitHandle.Set()
端接头
公共子DoWait(LEVECONTEXT为布尔值)
信号为布尔值的Dim
waitHandle.Reset()
Console.WriteLine(“线程[{0:d4}]:正在等待…”,Thread.CurrentThread.GetHashCode()
signaled=waitHandle.WaitOne(3000,左上下文)
如果发出信号
Console.WriteLine(“线程[{0:d4}]:等待释放!!!”,Thread.CurrentThread.GetHashCode()
其他的
Console.WriteLine(“线程[{0:d4}]:等待超时!!!”,Thread.CurrentThread.GetHashCode()
如果结束
端接头
末级
公共类TestSyncDomainWait
公共共享子主目录()
将syncClass设置为新的SyncingClass()
朦胧如线
Console.WriteLine(vbNewLine+“同步域内的等待和信号:”+vbNewLine)
RunWater=新线程(RunWaitKeepContext的地址)
runWaiter.Start(同步类)
线程。睡眠(1000)
Console.WriteLine(“线程[{0:d4}]:信号…”,Thread.CurrentThread.GetHashCode())
'此信号调用将被阻止,直到DoWait中的超时过期。
syncClass.Signal()
runwater.Join()
Console.WriteLine(vbNewLine+“同步域外的等待和信号:”+vbNewLine)
runWaiter=新线程(RunWaitLeaveContext的地址)
runWaiter.Start(同步类)
线程。睡眠(1000)
Console.WriteLine(“线程[{0:d4}]:信号…”,Thread.CurrentThread.GetHashCode())
'此信号调用已解除阻止,并将等待句柄设置为
'释放等待线程。
syncClass.Signal()
runwater.Join()
端接头
公共共享子RunWaitKeepContext(parm作为对象)
作为SyncingClass=CType的Dim syncClass(参数,SyncingClass)
syncClass.DoWait(错误)
端接头
公共共享子RunWaitLeaveContext(parm作为对象)
作为SyncingClass=CType的Dim syncClass(参数,SyncingClass)
syncClass.DoWait(真)
端接头
末级
'示例程序的输出类似于以下内容:
' 
'同步域内的等待和信号:
' 
'线程[0004]:正在等待。。。
'线程[0001]:信号。。。
'线程[0004]:等待超时!!!
'线程[0001]:发送信号。。。
' 
'同步域外的等待和信号:
' 
'线程[0006]:正在等待。。。
'线程[0001]:信号。。。
'线程[0001]:发送信号。。。
'线程[0006]:等待释放!!!
请参见此处的更多详细信息:

这是线程中的一个大罪,被称为“热等待循环”。线程有许多罪恶,其中许多根本没有黄色胶带,但这一条特别阴险。主要的问题是让一个处理器内核烧得很热,在一个紧密的循环中测试IsRunning属性

当您使用x86 jitter时,这会引发一个非常严重的问题,它会在发布版本中生成代码,读取cpu寄存器中的IsRunning property backing field变量。并反复测试cpu寄存器值,而无需从字段重新加载值。这是最终的死锁,它永远无法退出循环。您通过编辑代码或使用调试器使其脱离该模式。为了避免这种情况,属性的backing字段必须声明为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

请注意,AutoResteEvent也丢失了黄色磁带。多次调用Set(),而另一个线程尚未调用WaitOne(),结果很糟糕。

您使用的是什么版本的.NET?4.0,但我可以相对轻松地切换到4.5。请不要忘记任务,尤其是wait Task.Yield(),它允许在UI线程上调用时运行UI线程的消息泵,或等待任务。延迟将暂停
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