views:

223

answers:

5

Hi guys,

In my app, I have a thread that runs continuously. By using Thread.Sleep(), the function executes every 10 minutes.

I need to be able to kill this thread when a user clicks a button. I know Thread.Abort() is not reliable. I can use a variable to stop the thread, but since it is sleeping it could be another 10 minutes before the thread kills itself.

Any ideas?

+9  A: 

Why don't you use a timer to schedule the task every ten minutes instead. That will run your code on a thread pool thread and thus you will not have to manage this yourself.

For more details see the System.Threading.Timer class.

Brian Rasmussen
Interesting. Can you give me a bit more details? a bit new to this.
whydna
You still have to solve the problem of cancelling the periodic action. But now it's disabling a Timer instead of ending a Thread.
Ben Voigt
I can cancel the action simply by disabling the timer, no?
whydna
@whydna This approach also highlights a subtle bug in your original code: it will not execute every 10 minutes but rather every (10 min + execution time). Depending on what you're doing this may or may not be significant.
CurtainDog
+4  A: 

Instead of Thread.Sleep use a System.Threading.ManualResetEvent. The WaitOne method has a timeout just like Thread.Sleep, your thread will sleep for that interval unless the event is triggered first, and the return value tells you whether the interval elapsed or the event was set.

Ben Voigt
+2  A: 

One possibility is to not have it sleep for ten minutes. Have it sleep for 10 seconds then only do its work on every sixtieth wakeup. Then you only have a latency of ten seconds before it stops.

Aside: This is not necessarily the best solution but it's probably the quickest to implement. As with all possibilities you should do a cost/benefit analysis when selecting which solution is right for you.

If ten seconds is still too much, you can drop it further although keep in mind that dropping it too far will result in a possible performance impact.

You're right that you shouldn't kill threads from outside, it's usually a recipe for disaster if you happen to do it while they have a lock on some resource that's not freed on kill. Threads should always be responsible for their own resources, including their lifetimes.

paxdiablo
+1 for nice suggestion.
Int3
+3  A: 

Building on Ben's answer, here's the pattern to help you out...

using System.Threading;

public class MyWorker {
        private ManualResetEvent mResetEvent = new ManualResetEvent(false);
        private volatile bool mIsAlive;
        private const int mTimeout = 6000000;

        public void Start()
        {
            if (mIsAlive == false)
            {
                mIsAlive = true;
                Thread thread = new Thread(new ThreadStart(RunThread));
                thread.Start();
            }
        }

        public void Stop()
        {
            mIsAlive = false;
            mResetEvent.Set();
        }

        public void RunThread()
        {
            while(mIsAlive)
            {
                //Reset the event -we may be restarting the thread.
                mResetEvent.Reset();

                DoWork();

                //The thread will block on this until either the timeout
                //expires or the reset event is signaled.
                if (mResetEvent.WaitOne(mTimeout))
                {
                    mIsAlive = false; // Exit the loop.
                }
            }
        }

        public void DoWork()
        {
            //...
        } }
Rob Cooke
There is a small problem with this sample. It waits for the timeout period as well as the time taken by `DoWork()`. If the work is anything longer than an instant the timing will be off. Should deduct the current time from (last-work-start-time + mTimeout); if negative don't wait at all and just loop back (job took longer than mTimeout).
devstuff
It's true, but since the OP was using a sleep and didn't mention a hard period requirement I didn't sync the period. This preserves the timing behavior while still allowing an external thread to kill the loop on demand. Certainly there are cases where either approach is appropriate -not to mention another case where you may skip the next period if the remaining duration in the current period is below some threshold.Edit: If DoWork() is taking anywhere near the period, he will have to evaluate mIsAlive somewhere else to kill it early. This code just breaks the "sleep" on demand.
Rob Cooke
Also, you should actually create the event somewhere. With classes simply declaring a variable is not enough to create an instance.
Ben Voigt
@Ben Voigt: Yeah... Fixed.
Rob Cooke
+3  A: 

So here's a sample that users timers to do the work as suggested by Brian. Use start/stop as needed. To cleanup the (Program) object once you are done with it make sure you call Dispose.

Just note that when you call Stop it will prevent the timer from firing again, however you still may have a worker thread in the middle of executing the timer_Elapsed handler, i.e. stopping the timer doesn't stop any currently executing worker thread.

using System;
using System.Timers;

namespace TimerApp
{
    class Program : IDisposable
    {
        private Timer timer;

        public Program()
        {
            this.timer = new Timer();
            this.timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            this.timer.AutoReset = true;
            this.timer.Interval = TimeSpan.FromMinutes(10).TotalMilliseconds;
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            // TODO...your periodic processing, executed in a worker thread.
        }

        static void Main(string[] args)
        {
            // TODO...your app logic.
        }

        public void Start()
        {
            this.timer.Start();
        }

        public void Stop()
        {
            this.timer.Stop();
        }

        public void Dispose()
        {
            this.timer.Dispose();
        }
    }
}
donovan