重複を防ぐためにタイマーを同期
-
22-08-2019 - |
質問
私は間隔で可変長アクティビティ(データベースのスキャンや更新を)実行するWindowsサービスを書いています。私は頻繁に実行するには、このタスクが必要ですが、処理するコードは、同時に複数回実行しても安全ではありません。
死刑執行を重ねることはありませんしながら、は、どのように私は、最も簡単に30秒ごとにタスクを実行するためにタイマーを設定することができますか? (私はSystem.Threading.Timer
がこの仕事のための正しいタイマーであると仮定していますが、誤解される可能性があり)ます。
解決
あなたはタイマーでそれを行うことができますが、あなたは、データベースのスキャンや更新時にロックのいくつかのフォームを持っている必要があります。同期するための簡単なlock
が発生する複数の実行を防ぐのに十分かもしれません。
言われていること、あなたが運転している後、タイマをスタートする方が良いかもしれません完了し、ちょうどそれを一度使用し、それを止めます。あなたの次の操作の後、それを再起動します。これは、オーバーラップのチャンスがないと、ロックなしで、あなたのイベントの間に30秒(またはN秒)を与えるだろう。
例:
System.Threading.Timer timer = null;
timer = new System.Threading.Timer((g) =>
{
Console.WriteLine(1); //do whatever
timer.Change(5000, Timeout.Infinite);
}, null, 0, Timeout.Infinite);
の直後に作業.....完了... 5秒待って....完了... 5秒待って.... の
.....すぐに作業他のヒント
私はあなたの経過コードでMonitor.TryEnterを使用すると思います:
if (Monitor.TryEnter(lockobj))
{
try
{
// we got the lock, do your work
}
finally
{
Monitor.Exit(lockobj);
}
}
else
{
// another elapsed has the lock
}
私は、このようなもののためにSystem.Threading.Timer
を好むます:
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000);
object updateLock = new object();
void UpdateCallback(object state)
{
if (Monitor.TryEnter(updateLock))
{
try
{
// do stuff here
}
finally
{
Monitor.Exit(updateLock);
}
}
else
{
// previous timer tick took too long.
// so do nothing this time through.
}
}
あなたはそれがすべての更新の後、タイマーにワンショットと再起動を行うことで、ロックの必要性を排除することができます:
// Initialize timer as a one-shot
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite);
void UpdateCallback(object state)
{
// do stuff here
// re-enable the timer
UpdateTimer.Change(30000, Timeout.Infinite);
}
の代わりに(あなたの時限スキャンのすべてが待って最終的に積み重ねさせる可能性がある)、ロックの。あなたがスレッドでスキャン/更新を開始してからちょうどスレッドがまだ生きているかどうかを確認するためのチェックを行うことができます。
Thread updateDBThread = new Thread(MyUpdateMethod);
...
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!updateDBThread.IsAlive)
updateDBThread.Start();
}
次のようにあなたはAutoResetEventを使用することができます:
// Somewhere else in the code
using System;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(30000, false))
{
// Signalled time to quit
return;
}
else
{
// grab a lock
// do the work
// Whatever...
}
}
}
擬似コードに従うように、わずかに「スマートな」溶液である:
using System;
using System.Diagnostics;
using System.Threading;
// In the class or whever appropriate
static AutoResetEvent autoEvent = new AutoResetEvent(false);
void MyWorkerThread()
{
Stopwatch stopWatch = new Stopwatch();
TimeSpan Second30 = new TimeSpan(0,0,30);
TimeSpan SecondsZero = new TimeSpan(0);
TimeSpan waitTime = Second30 - SecondsZero;
TimeSpan interval;
while(1)
{
// Wait for work method to signal.
if(autoEvent.WaitOne(waitTime, false))
{
// Signalled time to quit
return;
}
else
{
stopWatch.Start();
// grab a lock
// do the work
// Whatever...
stopwatch.stop();
interval = stopwatch.Elapsed;
if (interval < Seconds30)
{
waitTime = Seconds30 - interval;
}
else
{
waitTime = SecondsZero;
}
}
}
}
これらのいずれかがちょうどイベントをシグナリングすることによって、あなたがシャットダウンスレッドをすることができるという利点があります。
<時間>編集
私は、そうでない場合は、同時に実行します、このコードは、あなたがこれらのみMyWorkerThreadsの1()実行しているという仮定を作ることを、追加する必要があります。
私は、単一の実行を思っていた時に、ミューテックスを使用しました。
private void OnMsgTimer(object sender, ElapsedEventArgs args)
{
// mutex creates a single instance in this application
bool wasMutexCreatedNew = false;
using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew))
{
if (wasMutexCreatedNew)
{
try
{
//<your code here>
}
finally
{
onlyOne.ReleaseMutex();
}
}
}
}
申し訳ありませんが、私はそう遅く...あなたはGetMutexName()メソッド呼び出しの一部として、ミューテックスの名前を提供する必要がありますよ。