2012-11-22 184 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