Don't poll

Thu, November 11, 2004, 08:09 AM under dotNET
Sometimes we write code that uses polling. A brief example of that is some method/routine that runs on a timer (e.g. every 5 seconds), reads new entries from a file/database and performs some operation based on the input - and repeates this cycle until we stop the timer.

Although there are valid scenarios for taking the approach described above, there are others where it is a sub-optimal solution. For example, if you need to know as soon as there is a new entry to process, then there is no point waiting for your timer's tick; conversly, if there are no new items to process then there is no point running the function. A similar (equally not good) alternative is instead of running a timer to make the thread sleep for a while.

So in those case where the 2 aforementioned similar solutions are not the best, what is the correct way? Use a thread! The following code shows you an example of how. Note that I use this technique with the Compact Framework so, for example, we need the extra boolean to tell the thread to kill itself/exit since the CF 1.0 threads are missing many properties that could help (e.g. IsBackground, Abort etc).

        // declarations
        private Thread mSendNextThread;            // worker thread
        private bool mStayAlive;    // for controlling the thread termination
        private Queue mQue;                        // buffer of jobs
        private ManualResetEvent mWaitEvent;// signal the thread to wake up
 
        // Initialisation logic
        public void Start(){
            Console.WriteLine("*********");
            Console.WriteLine("Start");
            mQue = new Queue();
            mWaitEvent = new ManualResetEvent(false);
            mStayAlive = true;
            mSendNextThread = new Thread(new ThreadStart(OnMyOwnThread));
            mSendNextThread.Start();
        }
 
        // Tear down logic
        public void Stop(){
            Console.WriteLine("Stop request");
            lock (mQue.SyncRoot){
                mQue.Clear();
 
                // kill the thread
                mWaitEvent.Reset();
                Thread.Sleep(10);
                mStayAlive = false;
                mWaitEvent.Set();
                Thread.Sleep(10);
                mSendNextThread = null;
            }
            Console.WriteLine("Stopped");
        }
 
        // queue more jobs
        public void MoreInput(object someInput){
            Console.WriteLine("More Input: " + someInput.ToString());
            lock (mQue.SyncRoot){
                mQue.Enqueue(someInput);
            }
 
            mWaitEvent.Set();
        }
 
        // process jobs
        public void OnMyOwnThread(){
            while (mStayAlive){
                if (mWaitEvent.WaitOne()){
 
                    mWaitEvent.Reset();
                    while (mQue.Count > 0){    //Whether we do the looping depends
                                            //on the specifics of DoWork
                        if (!mStayAlive){
                            break;
                        }
 
                        this.DoWork2();
                        mWaitEvent.Reset();
                    }
                }
            }
            Console.WriteLine("Thread exiting");
        }
 
        private void DoWork2(){
            object queObj = null;
            lock (mQue.SyncRoot){
                if (mQue.Count > 0){
                    queObj = mQue.Dequeue();
                }
            }
 
            // do some long processing with this job/input
            if (queObj != null){
                Console.WriteLine("Processing: " + queObj.ToString());
                Thread.Sleep(300);//LONG PROCESSING
                Console.WriteLine("Processed " + Environment.TickCount.ToString());
            }
        }