2012-11-22 139 views
0

我有一个类在指定时间后引发事件(它在内部使用System.Timers.Timer)。在我的测试代码中,我创建了一个Stopwatch,这是我在创建课程之前开始的,并设置了事件的回调以停止Stopwatch。然后,我封锁,直到Not Stopwatch.IsRunning。很简单,对吧?如何安全地阻止.NET线程

我原来的阻塞代码为

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

您使用的是什么版本的.NET? – Eilistraee

+0

4.0但我可以相对容易切换到 –

+0

然后不要忘记任务,尤其是等待Task.Yield(),它允许UI线程的消息泵在UI线程上调用时运行,或等待任务。延迟(延迟),暂停执行而不保持当前线程像睡眠一样被阻塞。我不会详细说明,因为你的问题的真正答案是真正的汉斯。 – Eilistraee

回答

9
While Stopwatch.IsRunning 
End While 

它是在线程的大罪之一,被称为 “热等待循环”。线程有许多罪过,其中许多人根本没有黄色磁带,但这一个特别阴险。主要的问题是你让一个处理器核心烧红,在一个严格的循环中测试IsRunning属性。

当您使用的x86抖动这相生一个很讨厌的问题,它的发行版本,读取在CPU寄存器,将isRunning属性支持字段变量生成代码。并且一次又一次地测试cpu寄存器的值,而不需要从字段重新加载值。这是最终的僵局,它永远不会退出循环。您通过编辑代码或使用调试器将其从该模式中移除。为了避免它,该属性的后台字段必须被声明为易失性但这不是你在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有黄色胶带缺失也是如此。不止一次调用Set()而另一个线程尚未调用WaitOne()最终效果不佳。

+0

你能否澄清你的意思是“黄色磁带”? –

+1

曾经在电视上看过美国犯罪秀,如法律与秩序?犯罪现场用黄色胶带封锁,以防止任何人误入并破坏证据。换句话说:“注意,这里发生了不寻常的事情!” –

0

你可以尝试在你的死循环增加

Thread.Sleep(0) 

+0

这很有效,但是......如果它是为'0'睡觉,那实际上会做什么? –

+0

你的空循环可能被编译出来,所以它从来没有被执行,0的睡眠将(应该)保持编译,但不应该花你任何睡眠时间 – Mike

+0

睡眠(0)将放弃时间片的其余部分赋予你的线程,让其他线程可以工作。你感冒也睡了很短的时间。 – Joe

0

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

而之所以你的空循环没有工作,可能是因为在编译期间JIT看到,这个循环是空的,并删除它(只是猜测)优化你的代码。

+0

当循环为空时程序无限期挂起,所以它绝对没有被优化。 –

+0

我的歉意,我误解了你的代码行为。其他解释可能是,'Console.Writeline'执行I/O操作,允许第二个(定时器)线程执行它。如果你使你的'while'循环的主线程(一个地方)比Timer的线程具有更高的优先级,并且它不给他任何时间片来执行回调?但可能使用@Abdias Software建议的方法,而不是SpinWait或Thread.Sleep将是最好的。 –

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