2

次のコードTimerRecalcStatisticsElapsedでは、実行中のインスタンスを 1 つだけ持つ必要があります。このコールバックが呼び出すワーカー メソッドは、一度に最大 1 つのスレッドが実行されるように、順番に実行されます。

質問パート 1:

タイマーのコールバックがスレッドプール スレッドを実行する場合 (別のスレッドでコールバックを実行するのではなく)、スレッドプールがキューに入れられ、条件に基づいて後で実行するためにスレッドを延期する可能性があると言うのは正しいですか (MaxThreads に達した、スレッドプールの内部ロジック) ?

質問パート 2:

1 つのタイマー コールバックが即時実行以外のキューに入れられる可能性があると仮定すると、それは任意の数のスレッド コールバックが同時に実行される可能性があることを意味しますか?

質問パート3

パート 2 が正しいと仮定すると、以下のコードで複数のコールバックが同時に動作する可能性があるということですか?

私が質問している理由は、マルチ CPU サーバーで実行されているこのクラスのインスタンスが数千あるためです。の順不同の操作と一致するデータ破損も見られます// Do Work Here

さておき

// Do work hereSystem.Collections.Dictionary と内部的に連携し、y の値を編集します。また、シリアルに呼び出される後続の関数のいくつかのキーも削除されます。その関数には、最初の呼び出しで以前に存在していたキー (x) がありません。これは、最終ステートメントで競合状態があるためだと思いますobj.cleanupdata()

public class SystemTimerTest
   {

    readonly System.Timers.Timer timerRecalcStatistics;
    readonly System.Diagnostics.Stopwatch stopwatchForRecalcStatistics = new System.Diagnostics.Stopwatch();


    public SystemTimerTest(TimeSpan range, DataOverwriteAction action)
    {
        int recalculateStatisticsEveryXMillseconds = 1000;

        timerRecalcStatistics = new System.Timers.Timer(recalculateStatisticsEveryXMillseconds);
        timerRecalcStatistics.AutoReset = true;
        timerRecalcStatistics.Elapsed += new System.Timers.ElapsedEventHandler(TimerRecalcStatisticsElapsed);
        timerRecalcStatistics.Interval = recalculateStatisticsEveryXMillseconds;
        timerRecalcStatistics.Enabled = true;


        this.maxRange = range;
        this.hashRunningTotalDB = new HashRunningTotalDB(action);
        this.hashesByDate = new HashesByDate(action);
        this.dataOverwriteAction = action;
    }


    private void TimerRecalcStatisticsElapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        stopwatchForRecalcStatistics.Start();
        Console.WriteLine("The TimerRecalcStatisticsElapsed event was raised at {0}", e.SignalTime.ToString("o"));

         // DO WORK HERE


        stopwatchForRecalcStatistics.Stop();
        double timeBuffer  = GetInterval(IntervalTypeEnum.NearestSecond, e.SignalTime) - stopwatchForRecalcStatistics.ElapsedMilliseconds;

        if (timeBuffer > 0)
            timerRecalcStatistics.Interval = timeBuffer;
        else
            timerRecalcStatistics.Interval = 1;

        stopwatchForRecalcStatistics.Reset();         
        timerRecalcStatistics.Enabled = true;
    }
 }
4

2 に答える 2

4

広告 1) ThreadPool がコールバック メソッドの実行を延期できるかどうかは重要ではありません。いずれにせよ、別のタイマー間隔が経過する前にコールバックが実行を完了することが保証されていないためです (たとえば、スレッド スケジューラによってスレッドが一時停止されたり、コールバックが long-実行機能)。

広告 2) これは、MSDN がTimerクラスについて述べていることです。

SynchronizingObject プロパティが null の場合、ThreadPool スレッドで Elapsed イベントが発生します。Elapsed イベントの処理が Interval より長く続く場合、イベントは別の ThreadPool スレッドで再び発生する可能性があります。この場合、イベント ハンドラーは再入可能にする必要があります。

答えはイエスです。コールバックは複数のスレッドで同時に実行できます。

広告 3) はい。また、コールバック メソッドで共有リソース (timerRecalcStatistics、stopwatchForRecalcStatistics) を使用しないようにするか、これらの共有リソースへのアクセスを同期するか (ロックなどを使用して)、適切なオブジェクトを Timer のSynchronizingObjectプロパティに設定するか、Timer の AutoReset プロパティを false に設定する必要があります (およびタイマー コールバックの最後に再びタイマーを有効にします)。

更新:Jon Skeetの答えはあなたの問題を解決しないと思います。また、独自の SynchronizingObject を実装することは、必要以上に複雑です (ただし、問題全体を知らずに言うのは難しいです)。この実装が機能することを願っています (ただし、テストはしていません):

public class MySynchronizeInvoke : ISynchronizeInvoke
{
    private object SyncObject = new Object();
    private delegate object InvokeDelegate(Delegate method, object[] args);

    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        ElapsedEventHandler handler = (ElapsedEventHandler)method;
        InvokeDelegate D = Invoke;
        return D.BeginInvoke(handler, args, CallbackMethod, null);
    }

    private void CallbackMethod(IAsyncResult ar)
    {
        AsyncResult result = ar as AsyncResult;
        if(result != null)
            ((InvokeDelegate)result.AsyncDelegate).EndInvoke(ar);
    }

    public object EndInvoke(IAsyncResult result)
    {
        result.AsyncWaitHandle.WaitOne();
        return null;
    }

    public object Invoke(Delegate method, object[] args)
    {
        lock(SyncObject)
        {
            ElapsedEventHandler handler = (ElapsedEventHandler)method;
            handler(args[0], (ElapsedEventArgs)args[1]);
            return null;
        }
    }

    public bool InvokeRequired
    {
        get { return true; }
    }
}
于 2012-05-18T14:49:08.060 に答える
3

System.Timers.Timer のドキュメントから:

SynchronizingObject プロパティが null の場合、ThreadPool スレッドで Elapsed イベントが発生します。Elapsed イベントの処理が Interval より長く続く場合、イベントは別の ThreadPool スレッドで再び発生する可能性があります。この場合、イベント ハンドラーは再入可能にする必要があります。

だからあなたの質問に答えるために:

  1. はい、スレッドプールスレッドで実行され、スレッドプールがいっぱいになり、他のものと同様に延期される可能性があります。現在、スレッドプールに最大で数百のスレッドがあることを考えると、これは問題になりません。もしそうなら、あなたはより大きな問題を抱えています。

  2. 同期オブジェクトを設定しないか、コールバックを同期しないと仮定すると、はい、複数のコールバックが重複する可能性があります。タイマーに同期オブジェクトを指定すると、イベントが「重複」しません。

  3. あなたが提供したコードは、そのコールバックを決して同期しません。そのため、コールバックのコピーを複数同時に実行することができます。クラスのすべてのインスタンスを互いに同期させたい場合は、lock ステートメントのようなものを使用してメソッドを同期する必要があります。クラスの個々のインスタンスごとにコールバックを 1 つだけ実行したい場合は、タイマーの SynchronizingObject を使用する必要があります。いつでも。

于 2012-05-18T14:45:39.853 に答える