0

指定された時間の後にイベントを発生させるクラスがあります (System.Timers.Timer内部を使用します)。私のテストコードではStopwatch、クラスが作成される前に開始した を作成し、イベントのコールバックを設定してそれを停止しましたStopwatch。その後、までブロックNot Stopwatch.IsRunningしました。シンプルですね。

私の元のブロックコードは

While Stopwatch.IsRunning
End While

しかし、そのような空の 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

4 に答える 4

9
While Stopwatch.IsRunning
End While

これは、「ホット ウェイト ループ」と呼ばれる、スレッド化における大罪の 1 つです。スレッドには多くの罪があり、その多くは黄色いテープがまったくありませんが、これは特に陰湿です。主な問題は、IsRunning プロパティをタイトなループでテストしながら、1 つのプロセッサ コアを真っ赤に焼き続けていることです。

これは、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() を複数回呼び出すと、うまくいきません。

于 2012-11-22T21:38:00.267 に答える
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を参照してください。

于 2012-11-22T21:14:45.543 に答える
0

追加してみてください

Thread.Sleep(0)

あなたのタイトなループで。

于 2012-11-22T21:10:07.617 に答える
0

本当にアクティブな待機が必要な場合は、SpinWait構造を使用できます。

また、空のループが機能しなかった理由は、コンパイル中にJITがこのループが空であることを確認し、それを削除してコードを最適化したためである可能性があります(推測)。

于 2012-11-22T21:10:42.070 に答える