可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm writing a Windows service that runs a variable length activity at intervals (a database scan and update). I need this task to run frequently, but the code to handle isn't safe to run multiple times concurrently.
How can I most simply set up a timer to run the task every 30 seconds while never overlapping executions? (I'm assuming System.Threading.Timer
is the correct timer for this job, but could be mistaken).
回答1:
You could do it with a Timer, but you would need to have some form of locking on your database scan and update. A simple lock
to synchronize may be enough to prevent multiple runs from occurring.
That being said, it might be better to start a timer AFTER you're operation is complete, and just use it one time, then stop it. Restart it after your next operation. This would give you 30 seconds (or N seconds) between events, with no chance of overlaps, and no locking.
Example :
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);
Work immediately .....Finish...wait 5 sec....Work immediately .....Finish...wait 5 sec....
回答2:
I'd use Monitor.TryEnter in your elapsed code:
if (Monitor.TryEnter(lockobj))
{
try
{
// we got the lock, do your work
}
finally
{
Monitor.Exit(lockobj);
}
}
else
{
// another elapsed has the lock
}
回答3:
I prefer System.Threading.Timer
for things like this, because I don't have to go through the event handling mechanism:
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.
}
}
You can eliminate the need for the lock by making the timer a one-shot and re-starting it after every update:
// 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);
}
回答4:
instead of locking (which could cause all of your timed scans to wait and eventually stack up). You could start the scan/update in a thread and then just do a check to see if the thread is still alive.
Thread updateDBThread = new Thread(MyUpdateMethod);
...
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!updateDBThread.IsAlive)
updateDBThread.Start();
}
回答5:
You could use the AutoResetEvent as follows:
// 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...
}
}
}
A slightly "smarter" solution is as follow in pseudo-code:
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;
}
}
}
}
Either of these has the advantage that you can shutdown the thread, just by signaling the event.
Edit
I should add, that this code makes the assumption that you only have one of these MyWorkerThreads() running, otherwise they would run concurrently.
回答6:
I've used a mutex when I've wanted single execution:
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();
}
}
}
}
Sorry I'm so late...You will need to provide the mutex name as part of the GetMutexName() method call.