如何安全地阻止.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