web-dev-qa-db-ja.com

System.Threading.Timerを確実に停止しますか?

さて、私はこれに対する解決策をたくさん探しました。 System.Threading.Timerのコールバックメソッドが停止した後に呼び出されるのを防ぐ、クリーンでシンプルな方法を探しています。

私は何も見つけられないようであり、これは時々、恐ろしいthread-thread.sleep-thread.abort combo shuddersに頼るようになりました。

ロックを使用して実行できますか?これを行う良い方法を見つけるのを手伝ってください。ありがとう

52
JayPea

like Conrad Frix 代わりにSystem.Timers.Timerクラスを使用することを提案しました。

private System.Timers.Timer _timer = new System.Timers.Timer();
private volatile bool _requestStop = false;

public constructor()
{
    _timer.Interval = 100;
    _timer.Elapsed += OnTimerElapsed;
    _timer.AutoReset = false;
    _timer.Start();
}

private void OnTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // do work....
    if (!_requestStop)
    {
        _timer.Start();//restart the timer
    }
}

private void Stop()
{
    _requestStop = true;
    _timer.Stop();
}

private void Start()
{
    _requestStop = false;
    _timer.Start();
}
41
Jalal Said

より簡単な解決策は、Timerを決して再開しないように設定することです。メソッド Timer.Change は、タイマーを再起動しないように指示するdueTimeおよびperiodの値を取ることができます。

this.Timer.Change(Timeout.Infinite, Timeout.Infinite);

使用するように変更しながら System.Timers.Timer は「より良い」解決策かもしれませんが、それが実際的でないときが常にあるでしょう。 Timeout.Infinite で十分です。

103
Owen Blacker

System.Threading.Timerの場合、次のことができます(また、コールバックメソッドが破棄されたタイマー-ObjectDisposedExceptionで動作しないように保護します):

class TimerHelper : IDisposable
{
    private System.Threading.Timer _timer;
    private readonly object _threadLock = new object();

    public event Action<Timer,object> TimerEvent;

    public void Start(TimeSpan timerInterval, bool triggerAtStart = false,
        object state = null)
    {
        Stop();
        _timer = new System.Threading.Timer(Timer_Elapsed, state,
            System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

        if (triggerAtStart)
        {
            _timer.Change(TimeSpan.FromTicks(0), timerInterval);
        }
        else
        {
            _timer.Change(timerInterval, timerInterval);
        }
    }

    public void Stop(TimeSpan timeout = TimeSpan.FromMinutes(2))
    {
        // Wait for timer queue to be emptied, before we continue
        // (Timer threads should have left the callback method given)
        // - http://woowaabob.blogspot.dk/2010/05/properly-disposing-systemthreadingtimer.html
        // - http://blogs.msdn.com/b/danielvl/archive/2011/02/18/disposing-system-threading-timer.aspx
        lock (_threadLock)
        {
            if (_timer != null)
            {
                ManualResetEvent waitHandle = new ManualResetEvent(false)
                if (_timer.Dispose(waitHandle))
                {
                   // Timer has not been disposed by someone else
                   if (!waitHandle.WaitOne(timeout))
                      throw new TimeoutException("Timeout waiting for timer to stop");
                }
                waitHandle.Close();   // Only close if Dispose has completed succesful
                _timer = null;
            }
        }
    }

    public void Dispose()
    {
        Stop();
        TimerEvent = null;
    }

    void Timer_Elapsed(object state)
    {
        // Ensure that we don't have multiple timers active at the same time
        // - Also prevents ObjectDisposedException when using Timer-object
        //   inside this method
        // - Maybe consider to use _timer.Change(interval, Timeout.Infinite)
        //   (AutoReset = false)
        if (Monitor.TryEnter(_threadLock))
        {
            try
            {
                if (_timer==null)
                    return;

                Action<Timer, object> timerEvent = TimerEvent;
                if (timerEvent != null)
                {
                    timerEvent(_timer, state);
                }
            }
            finally
            {
                Monitor.Exit(_threadLock);
            }
        }
    }
}

これはそれを使用する方法です:

void StartTimer()
{
    TimerHelper _timerHelper = new TimerHelper();
    _timerHelper.TimerEvent += (timer,state) => Timer_Elapsed();
    _timerHelper.Start(TimeSpan.FromSeconds(5));
    System.Threading.Sleep(TimeSpan.FromSeconds(12));
    _timerHelper.Stop();
}

void Timer_Elapsed()
{
   // Do what you want to do
}
10
Rolf Kristensen

MSDN Docs は、Dispose(WaitHandle)メソッドを使用してタイマーを停止し、コールバックが呼び出されなくなったときに通知されることを示唆しています。

7
Will A

価値があるもののために、このパターンをかなり使います:

// set up timer
Timer timer = new Timer(...);
...

// stop timer
timer.Dispose();
timer = null;
...

// timer callback
{
  if (timer != null)
  {
    ..
  }
}

この回答はSystem.Threading.Timerに関連しています

_System.Threading.Timer_の処理をネット全体で同期する方法について、私は多くのナンセンスを読みました。そのため、状況を多少修正するためにこれを投稿しています。私が書いているものが間違っている場合は、遠慮なく教えてください/電話してください;-)

落とし穴

私の意見では、これらの落とし穴があります:

  • Timer.Dispose(WaitHandle)はfalseを返すことができます。すでに破棄されている場合(ソースコードを確認する必要がありました)にそうします。その場合、それはしませんWaitHandleを設定します-待ってはいけません!
  • WaitHandleタイムアウトを処理していません。真剣に-タイムアウトに興味がない場合に何を待っていますか?
  • 前述の同時実行性の問題 ここではmsdnでObjectDisposedExceptionが発生する可能性があるduring(後ではなく)廃棄。
  • Timer.Dispose(WaitHandle)は、-Slimウェイトハンドルでは正しく機能しないか、期待どおりに機能しません。たとえば、次はnot動作します(永久にブロックされます):
_ using(var manualResetEventSlim = new ManualResetEventSlim)
 {
     timer.Dispose(manualResetEventSlim.WaitHandle);
     manualResetEventSlim.Wait();
 }
_

溶液

タイトルはちょっと「太字」だと思いますが、以下はこの問題に対処するための試みです。二重処理、タイムアウト、ObjectDisposedExceptionを処理するラッパーです。ただし、Timerのすべてのメソッドを提供するわけではありませんが、自由に追加してください。

_internal class Timer
{
    private readonly TimeSpan _disposalTimeout;

    private readonly System.Threading.Timer _timer;

    private bool _disposeEnded;

    public Timer(TimeSpan disposalTimeout)
    {
        _disposalTimeout = disposalTimeout;
        _timer = new System.Threading.Timer(HandleTimerElapsed);
    }

    public event Signal Elapsed;

    public void TriggerOnceIn(TimeSpan time)
    {
        try
        {
            _timer.Change(time, Timeout.InfiniteTimeSpan);
        }
        catch (ObjectDisposedException)
        {
            // race condition with Dispose can cause trigger to be called when underlying
            // timer is being disposed - and a change will fail in this case.
            // see 
            // https://msdn.Microsoft.com/en-us/library/b97tkt95(v=vs.110).aspx#Anchor_2
            if (_disposeEnded)
            {
                // we still want to throw the exception in case someone really tries
                // to change the timer after disposal has finished
                // of course there's a slight race condition here where we might not
                // throw even though disposal is already done.
                // since the offending code would most likely already be "failing"
                // unreliably i personally can live with increasing the
                // "unreliable failure" time-window slightly
                throw;
            }
        }
    }

    private void HandleTimerElapsed(object state)
    {
        Elapsed.SafeInvoke();
    }

    public void Dispose()
    {
        using (var waitHandle = new ManualResetEvent(false))
        {
            // returns false on second dispose
            if (_timer.Dispose(waitHandle))
            {
                if (!waitHandle.WaitOne(_disposalTimeout))
                {
                    throw new TimeoutException(
                        "Timeout waiting for timer to stop. (...)");
                }
                _disposeEnded = true;
            }
        }
    }
}
_
3

私には、これが正しい方法のようです:タイマーを使い終わったら、disposeを呼び出すだけです。それはタイマーを停止し、将来のスケジュールされた呼び出しを防ぎます。

以下の例を参照してください。

class Program
{
    static void Main(string[] args)
    {
        WriteOneEverySecond w = new WriteOneEverySecond();
        w.ScheduleInBackground();
        Console.ReadKey();
        w.StopTimer();
        Console.ReadKey();
    }
}

class WriteOneEverySecond
{
    private Timer myTimer;

    public void StopTimer()
    {
        myTimer.Dispose();
        myTimer = null;
    }

    public void ScheduleInBackground()
    {
        myTimer = new Timer(RunJob, null, 1000, 1000);
    }

    public void RunJob(object state)
    {
        Console.WriteLine("Timer Fired at: " + DateTime.Now);
    }
}
3
user4908274

おそらくあなたは反対を行う必要があります。 system.timers.timerを使用して、AutoResetをfalseに設定し、必要な場合にのみ開始します

1
Conrad Frix

タイマーを停止することになっているコードが、タイマーイベントの呼び出しの前に実行されることを保証することはできません。たとえば、時間0で、時間5が来たときにイベントを呼び出すようにタイマーを初期化したとします。その後、時間3に、あなたはもはや電話を必要としないと決めました。そして、ここに書きたいメソッドを呼び出しました。その後、メソッドがJIT-tedされたときに、時刻4が来ます。OSは、スレッドがタイムスライスを使い果たし、切り替えることを決定します。そして、タイマーはどのように試行してもイベントを呼び出します-最悪のシナリオではコードが実行される機会がありません。

そのため、イベントハンドラーでロジックを提供する方が安全です。おそらく、イベントの呼び出しが不要になったらすぐにリセットされるManualResetEventがあります。したがって、タイマーを破棄してから、ManualResetEventを設定します。タイマーイベントハンドラーで最初に行うことは、ManualResetEventのテストです。リセット状態の場合-すぐに戻ります。したがって、コードの望ましくない実行を効果的に防ぐことができます。

1
Ivan Danilov

停止タイマーを正しく実現する方法については、MSDNリンクがあります。 syncPoint静的クラス変数によって同期されるControlThreadProc()イベントでHandleElapsed(object sender, ElapsedEventArgs e)メソッドを使用します。 Thread.Sleep(testRunsFor);が適切でない場合は、ControlThreadProc()のコメントを外します(おそらく)。重要なのは、静的ステートメントと、条件ステートメントでInterlocked.CompareExchangeなどのアトミック操作を使用することです。

リンク: Timer.Stopメソッド

このようなクラスを作成し、たとえばコールバックメソッドから呼び出すことで、タイマーを停止できます。

public class InvalidWaitHandle : WaitHandle
{
    public IntPtr Handle
    {
        get { return InvalidHandle; }
        set { throw new InvalidOperationException(); }
    }
}

タイマーのインスタンス化:

_t = new Timer(DisplayTimerCallback, TBlockTimerDisplay, 0, 1000);

次に、コールバックメソッド内:

if (_secondsElapsed > 80)
{
    _t.Dispose(new InvalidWaitHandle());
}
0
Stonetip