Alternatives to Thread.Sleep()
Every N minutes we want to run through a list of tasks. So we've created a task executor with a
do { DoWork(); }while(!stopRequested)
Now we want to have a pause between work cycles. Everyone seems to think Thread.Sleep() is the devil. I've seen mention of using Monitor/Event stuff but we don't have someone else telling us to do work. We just want to do stuff every N minutes like clockwork.
So is there an alternative or have I found a valid use of Thread.Sleep?
Someone 开发者_开发技巧in another post mentioned WaitHandle.WaitOne() as an alternative but you can't call that from a non-static method apparently? Or at least I can't because I get a compile time error of..
An object reference is required for the non-static field, method, or property 'System.Threading.WaitHandle.WaitOne(System.TimeSpan)'
By my understanding, Thread.Sleep() is bad because it forces the thread's resources out of the cache, so they have to be loaded again afterwards. Not a big deal, but it could aggravate performance issues in high-load situations. And then there's the fact that the timing isn't precise, and that it effectively can't wait for durations under about 10ms...
I use this snippet:
new System.Threading.ManualResetEvent(false).WaitOne(1000);
Easy as pie and it all fits on one line. Creates a new event handler that will never be set, and then waits the full timeout period, which you specify as the argument to WaitOne()
.
Although, for this specific scenario, a Timer would probably be a more appropriate approach:
var workTimer = new System.Threading.Timer(
(x) => DoWork(),
null,
1000, // initial wait period
300000); // subsequent wait period
Then, instead of setting a cancel variable, you would stop the timer with workTimer.Stop()
.
Edit:
Since people are still finding this useful, I should add that .NET 4.5 introduces the Task.Delay method, which is even more concise and also supports async:
Task.Delay(2000).Wait(); // Wait 2 seconds with blocking
await Task.Delay(2000); // Wait 2 seconds without blocking
You have to call WaitOne
on a WaitHandle
, certainly. It's an instance method. Otherwise how would it know what to wait for?
It's definitely better to have something you can react to instead of sleep, so that you can notice cancellation without waiting minutes for no reason. Another alternative to WaitHandle
is to use Monitor.Wait
/Pulse
.
However, if you're using .NET 4 I'd look into what the Task Parallel Library has to offer... it's at a slightly higher level than the other options, and is generally a well thought out library.
For regular work tasks you might want to look at using a Timer
(either System.Threading.Timer
or System.Timers.Timer
) or possibly even Quartz.NET.
Thread.Sleep
isn't the devil - you could use it for a scenario like this. It's just not very reliable for short durations.
Using a WaitHandle is a good option - but you need a specific instance of a wait handle. It won't do this alone, however.
That being said, most of the time, operations like this are better suited towards using a Timer.
The three options that I can think of off the top of my head are :
System.Threading.Timer
(More...)Monitor.Wait
(More...)System.Timers.Timer
(More...)
but I am sure as many will mention - Thread.Sleep()
isn't all that bad.
You could use an ManualResetEvent that you set when it's time to stop, and then do a WaitOne on it.
I would use a waiting timer which signals an AutoResetEvent. Your thread should wait for this WaitHandle object. Here is a small console app showing this approach:
class Program {
const int TimerPeriod = 5;
static System.Threading.Timer timer;
static AutoResetEvent ev;
static void Main(string[] args)
{
ThreadStart start = new ThreadStart(SomeThreadMethod);
Thread thr = new Thread(start);
thr.Name = "background";
thr.IsBackground = true;
ev = new AutoResetEvent(false);
timer = new System.Threading.Timer(
Timer_TimerCallback, ev, TimeSpan.FromSeconds(TimerPeriod), TimeSpan.Zero);
thr.Start();
Console.WriteLine(string.Format("Timer started at {0}", DateTime.Now));
Console.ReadLine();
}
static void Timer_TimerCallback(object state) {
AutoResetEvent ev = state as AutoResetEvent;
Console.WriteLine(string.Format
("Timer's callback method is executed at {0}, Thread: ",
new object[] { DateTime.Now, Thread.CurrentThread.Name}));
ev.Set();
}
static void SomeThreadMethod() {
WaitHandle.WaitAll(new WaitHandle[] { ev });
Console.WriteLine(string.Format("Thread is running at {0}", DateTime.Now));
}
}
If all your thread is doing is something like:
while (!stop_working)
{
DoWork();
Thread.Sleep(FiveMinutes);
}
Then I would suggest not using a thread at all. First, there's no particularly good reason to incur the system overhead of a dedicated thread that spends most of its time sleeping. Secondly, if you set the stop_working
flag 30 seconds after the thread stops sleeping, you'll have to wait four and a half minutes before the thread wakes up and terminates.
I'd suggest as others have: use a timer:
System.Threading.Timer WorkTimer;
// Somewhere in your initialization:
WorkTimer = new System.Threading.Timer((state) =>
{
DoWork();
}, null, TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0));
And to shut down:
WorkTimer.Dispose();
You could use a System.Timers.Timer and perform the work in its elapse handler.
As other answerers have said, Timers may work well for you.
If you do want your own thread, I wouldn't use Thread.Sleep here, if only because if you need to shut down the application, there's no good way to tell it to exit the sleep. I've used something like this before.
class IntervalWorker
{
Thread workerThread;
ManualResetEventSlim exitHandle = new ManualResetEventSlim();
public IntervalWorker()
{
this.workerThread = new Thread(this.WorkerThread);
this.workerThread.Priority = ThreadPriority.Lowest;
this.workerThread.IsBackground = true;
}
public void Start()
{
this.workerThread.Start();
}
public void Stop()
{
this.exitHandle.Set();
this.workerThread.Join();
}
private void WorkerThread()
{
int waitTimeMillis = 10000; // first work 10 seconds after startup.
while (!exitHandle.Wait(waitTimeMillis))
{
DoWork();
waitTimeMillis = 300000; // subsequent work at five minute intervals.
}
}
}
I've used both a Timer in a WinForms application and a console app started periodically by a ScheduledTask on a server. The WinForms with a timer has been used in cases when I want it to pop up notifications that it ran and what it found (I've set these to mimize to the systray). For situations where I only want to be notified if something goes wrong on a server, I've put them on the server as console apps and run them as Scheduled Tasks. That seems to work quite well.
I think I tried using Sleep in the apps where I now use Timers, and didn't like the result. For one thing, using a Timer I am able to call up the minimized app very easily in order to set or change settings on the front. If the app is asleep, you have difficulty regaining access while it is slumbering.
try {
TimeUnit.MILLISECONDS.sleep(1000L);
} catch (InterruptedException e) {
// handle
}
精彩评论