10 msごとに適切なイベントハンドラー(コールバック)を呼び出すSystem.Threading.Timerがあります。メソッド自体は再入可能ではないであり、場合によっては10ミリ秒よりはるかに長いかかることがあります。したがって、メソッドの実行中にタイマーを停止したいと思います。
コード:
private Timer _creatorTimer;
// BackgroundWorker's work
private void CreatorWork(object sender, DoWorkEventArgs e) {
_creatorTimer = new Timer(CreatorLoop, null, 0, 10);
// some other code that worker is doing while the timer is active
// ...
// ...
}
private void CreatorLoop(object state) {
// Stop timer (prevent reentering)
_creatorTimer.Change(Timeout.Infinite, 0);
/*
... Work here
*/
// Reenable timer
_creatorTimer.Change(10, 0);
}
MSDNは、コールバックメソッドがスレッドプールとは別のスレッドで(タイマーが起動するたびに)呼び出されると述べています。つまり、メソッドの最初のことでタイマーを停止しても、最初のインスタンスがタイマーを停止する前に、タイマーが起動してメソッドの別のインスタンスを実行することを必ずしも妨げることはありません。
タイマー(または再入可能でないメソッド自体)をロックする必要がありますか?コールバック(および非再入可能)メソッドの実行中にタイマーが起動しないようにする正しい方法は何ですか?
タイマーにコールバックメソッドの起動を継続させることはできますが、再入可能でないコードをMonitor.TryEnter/Exitでラップします。その場合、タイマーを停止/再起動する必要はありません。重複する呼び出しはロックを取得せず、すぐに戻りません。
private void CreatorLoop(object state)
{
if (Monitor.TryEnter(lockObject))
{
try
{
// Work here
}
finally
{
Monitor.Exit(lockObject);
}
}
}
いくつかの可能な解決策:
元のタイマーオブジェクトのChange()
メソッドを使用すると、新しいオブジェクトを破棄/作成せずにオプション#2を管理できる場合がありますが、Change()
最初のタイムアウトの期限が切れた後の新しい開始タイムアウト。それは1つか2つのテストの価値があるでしょう。
編集:
私はテストを行いました-再起動可能なワンショットとしてタイマーを操作することは完全に機能しているようで、他の方法よりもはるかに簡単です。これが出発点としてあなたに基づいたいくつかのサンプルコードです(私のマシンでコンパイルするためにいくつかの詳細が変更されている可能性があります):
private Timer _creatorTimer;
// BackgroundWorker's work
private void CreatorWork(object sender, EventArgs e) {
// note: there's only a start timeout, and no repeat timeout
// so this will fire only once
_creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite);
// some other code that worker is doing while the timer is active
// ...
// ...
}
private void CreatorLoop(object state) {
Console.WriteLine( "In CreatorLoop...");
/*
... Work here
*/
Thread.Sleep( 3000);
// Reenable timer
Console.WriteLine( "Exiting...");
// now we reset the timer's start time, so it'll fire again
// there's no chance of reentrancy, except for actually
// exiting the method (and there's no danger even if that
// happens because it's safe at this point).
_creatorTimer.Change(1000, Timeout.Infinite);
}
System.Timers.Timerでも同様の状況が発生しました。この場合、経過したイベントはスレッドプールから実行され、再入可能である必要があります。
私はこの方法を使用して問題を回避しました:
private void tmr_Elapsed(object sender, EventArgs e)
{
tmr.Enabled = false;
// Do Stuff
tmr.Enabled = true;
}
何をしているかに応じて、System.Timers.Timerを検討することをお勧めします。これは、 [〜#〜] msdn [〜#〜] からの素晴らしい要約です。
System.Windows.Forms System.Timers System.Threading
Timer event runs on what thread? UI thread UI or worker thread Worker thread
Instances are thread safe? No Yes No
Familiar/intuitive object model? Yes Yes No
Requires Windows Forms? Yes No No
Metronome-quality beat? No Yes* Yes*
Timer event supports state object? No No Yes
Initial timer event can be scheduled? No No Yes
Class supports inheritance? Yes Yes No
* Depending on the availability of system resources (for example, worker threads)
私はアトミック操作を提供するInterlockedでそれを行い、CompareExchangeによって、一度に1つのスレッドだけがクリティカルセクションに入ることを保証します。
private int syncPoint = 0;
private void Loop()
{
int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
//ensures that only one timer set the syncPoint to 1 from 0
if (sync == 0)
{
try
{
...
}
catch (Exception pE)
{
...
}
syncPoint = 0;
}
}
//using Timer with callback on System.Threading namespace
// Timer(TimerCallback callback, object state, int dueTime, int period);
// TimerCallback: delegate to callback on timer lapse
// state: an object containig information for the callback
// dueTime: time delay before callback is invoked; in milliseconds; 0 immediate
// period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable
// EXCEPTIONS:
// ArgumentOutOfRangeException: negative duration or period
// ArgumentNullException: callback parameter is null
public class Program
{
public void Main()
{
var te = new TimerExample(1000, 2000, 2);
}
}
public class TimerExample
{
public TimerExample(int delayTime, int intervalTime, int treshold)
{
this.DelayTime = delayTime;
this.IntervalTime = intervalTime;
this.Treshold = treshold;
this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime);
}
public int DelayTime
{
get;
set;
}
public int IntervalTime
{
get;
set;
}
public Timer Timer
{
get;
set;
}
public StateInfo SI
{
get;
set;
}
public int Treshold
{
get;
private set;
}
public void TimerCallbackWorker(object state)
{
var si = state as StateInfo;
if (si == null)
{
throw new ArgumentNullException("state");
}
si.ExecutionCounter++;
if (si.ExecutionCounter > this.Treshold)
{
this.Timer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold);
}
else
{
Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString());
}
}
public class StateInfo
{
public int ExecutionCounter
{
get;
set;
}
public DateTime LastRun
{
get
{
return DateTime.Now;
}
}
public override string ToString()
{
return this.LastRun.ToString();
}
}
}
// Result:
//
// 1 lapse, Time 2015-02-13 01:28:39 AM
// 2 lapse, Time 2015-02-13 01:28:41 AM
// -Timer stop, execution reached treshold 2
//