web-dev-qa-db-ja.com

遅延実行のためにThread.SleepとTimerを使用して比較

指定した時間実行を遅らせる方法があります。

使用すべきか

Thread thread = new Thread(() => {
    Thread.Sleep(millisecond);
    action();
});
thread.IsBackground = true;
thread.Start();

または

Timer timer = new Timer(o => action(), null, millisecond, -1);

私はいくつかの 記事 を読んでいたThread.Sleepはデザインが悪いです。しかし、私は本当に理由を理解していません。

ただし、タイマーを使用するために、タイマーにはdisposeメソッドがあります。実行が遅延するため、Timerの処理方法がわかりません。何か提案はありますか?

または、遅延実行のための代替コードがある場合もありがたいです。

1つの違いは、_System.Threading.Timer_は毎回新しいスレッドを作成するのではなく、スレッドプールスレッドでコールバックをディスパッチすることです。アプリケーションの実行中にこれを複数回行う必要がある場合、これにより、多数のスレッド(参照する記事が指摘しているように、リソースを大量に消費するプロセス)を作成および破棄するオーバーヘッドが節約されます。プール内のスレッドを再利用するだけで、一度に複数のタイマーを使用する場合は、同時に実行するスレッドが少なくなります(かなりのリソースを節約できます)。

つまり、Timerの方がはるかに効率的です。また、_Thread.Sleep_は、指定された時間だけLEASTで待機することが保証されているため、より正確になる可能性があります(OSがそれをより長い時間スリープ状態にする場合があります)。確かに、Timerはまだ正確ではありませんが、意図は可能な限り指定された時間に近いコールバックを起動することですが、これは必ずしも_Thread.Sleep_の意図ではありません。

Timerの破棄に関しては、コールバックはパラメーターを受け入れることができるため、Timer自体をパラメーターとして渡し、コールバックでDisposeを呼び出すことができます(これは試していませんが) -コールバック中にタイマーがロックされる可能性があると思います)。

編集:いいえ、Timerコンストラクター自体にコールバックパラメーターを指定する必要があるため、これはできません。

たぶんこんな感じ? (繰り返しますが、実際には試していません)

_class TimerState
{
    public Timer Timer;
}
_

...そしてタイマーを開始するには:

_TimerState state = new TimerState();

lock (state)
{
    state.Timer = new Timer((callbackState) => {
        action();
        lock (callbackState) { callbackState.Timer.Dispose(); }
        }, state, millisecond, -1);
}
_

ロックは、Timerフィールドが設定される前にタイマーコールバックがタイマーを解放しようとするのを防ぐ必要があります。


補遺:コメンターが指摘したように、action()がUIで何かを行う場合、_System.Windows.Forms.Timer_を使用する方がUIスレッドでコールバックを実行するため、おそらくより良い方法です。ただし、これが当てはまらず、_Thread.Sleep_対_Threading.Timer_の場合は、_Threading.Timer_が最適です。

42

つかいます - ThreadPool.RegisterWaitForSingleObject タイマーの代わりに:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true);
16
miniscalope

指定された時間だけアプリケーションを一時停止する場合は、Thread.Sleepで十分だと思います。人々がそれが悪いデザインだと言う理由は、ほとんどの場合、実際にアプリケーションを一時停止させたくないからだと思います。

たとえば、私は、プログラマがThread.Sleep(1000)を使用してソケットがメールを取得するまで待機するpop3クライアントで作業していました。そのような状況では、イベントハンドラーをソケットに接続し、ソケットの完了後にプログラムの実行を継続する方が適切でした。

14
Shawn

エリックのソリューションに似たソリューションを実装したことを覚えています。ただし、これは動作するものです;)

class OneTimer
    {
        // Created by Roy Feintuch 2009
        // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context
        public static void DoOneTime(ThreadStart cb, TimeSpan dueTime)
        {
            var td = new TimerDisposer();
            var timer = new Timer(myTdToKill =>
            {
                try
                {
                    cb();
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]");
                }
                finally
                {
                    ((TimerDisposer)myTdToKill).InternalTimer.Dispose();
                }
            },
                        td, dueTime, TimeSpan.FromMilliseconds(-1));

            td.InternalTimer = timer;
        }
    }

    class TimerDisposer
    {
        public Timer InternalTimer { get; set; }
    }
2
Froyke

私がSystem.Timerで持っている唯一の牛肉は、私がそれをポーリングサービスの長い遅延(時間、分)に使用するのを見たほとんどの時間であり、開発者はしばしばイベントを開始することを忘れますBefore彼らタイマーを開始します。これは、アプリまたはサービスを開始する場合、実際に実行する前にタイマーが経過するまで(時間、分)待機する必要があることを意味します。

確かに、これはタイマーの問題ではありませんが、誤用しやすいため、不適切に使用されることが多いと思います。

1
StingyJack

@miniscalopeいいえタイマーの代わりにThreadPool.RegisterWaitForSingleObjectを使用しないでください。System.Threading.Timerは、時間が経過し、待機ハンドルを必要としない場合、スレッドプールスレッドで実行されるコールバックをキューに入れます。イベントが通知されるか、タイムアウトが期限切れになるのを待ってから、スレッドがコールバックを呼び出すスレッドプールスレッドを結び付けます。

0
Matt