Example #1
0
        /// <summary>
        /// This method is executed on a dedicated a timer thread. Its purpose is
        /// to handle timer requests and notify the TimerQueue when a timer expires.
        /// </summary>
        private static void TimerThread()
        {
            AutoResetEvent    timerEvent   = s_timerEvent;
            List <TimerQueue> timersToFire = s_scheduledTimersToFire !;
            List <TimerQueue> timers;

            lock (timerEvent)
            {
                timers = s_scheduledTimers !;
            }

            int shortestWaitDurationMs = Timeout.Infinite;

            while (true)
            {
                timerEvent.WaitOne(shortestWaitDurationMs);

                int currentTimeMs = TickCount;
                shortestWaitDurationMs = int.MaxValue;
                lock (timerEvent)
                {
                    for (int i = timers.Count - 1; i >= 0; --i)
                    {
                        TimerQueue timer          = timers[i];
                        int        waitDurationMs = timer._scheduledDueTimeMs - currentTimeMs;
                        if (waitDurationMs <= 0)
                        {
                            timer._isScheduled = false;
                            timersToFire.Add(timer);

                            int lastIndex = timers.Count - 1;
                            if (i != lastIndex)
                            {
                                timers[i] = timers[lastIndex];
                            }
                            timers.RemoveAt(lastIndex);
                            continue;
                        }

                        if (waitDurationMs < shortestWaitDurationMs)
                        {
                            shortestWaitDurationMs = waitDurationMs;
                        }
                    }
                }

                if (timersToFire.Count > 0)
                {
                    foreach (TimerQueue timerToFire in timersToFire)
                    {
                        ThreadPool.UnsafeQueueUserWorkItemInternal(timerToFire, preferLocal: false);
                    }
                    timersToFire.Clear();
                }

                if (shortestWaitDurationMs == int.MaxValue)
                {
                    shortestWaitDurationMs = Timeout.Infinite;
                }
            }
        }
Example #2
0
        // Fire any timers that have expired, and update the native timer to schedule the rest of them.
        // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
        // to queue all but the first one.  The first may can then be invoked synchronously or queued,
        // a task left up to our caller, which might be firing timers from multiple queues.
        private void FireNextTimers()
        {
            // We fire the first timer on this thread; any other timers that need to be fired
            // are queued to the ThreadPool.
            TimerQueueTimer?timerToFireOnThisThread = null;

            lock (this)
            {
                // Since we got here, that means our previous timer has fired.
                _isTimerScheduled = false;
                bool haveTimerToSchedule = false;
                uint nextTimerDuration   = uint.MaxValue;

                long nowTicks = TickCount64;

                // Sweep through the "short" timers.  If the current tick count is greater than
                // the current threshold, also sweep through the "long" timers.  Finally, as part
                // of sweeping the long timers, move anything that'll fire within the next threshold
                // to the short list.  It's functionally ok if more timers end up in the short list
                // than is truly necessary (but not the opposite).
                TimerQueueTimer?timer = _shortTimers;
                for (int listNum = 0; listNum < 2; listNum++) // short == 0, long == 1
                {
                    while (timer != null)
                    {
                        Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "A timer in the list must have a valid due time.");

                        // Save off the next timer to examine, in case our examination of this timer results
                        // in our deleting or moving it; we'll continue after with this saved next timer.
                        TimerQueueTimer?next = timer._next;

                        long elapsed   = nowTicks - timer._startTicks;
                        long remaining = timer._dueTime - elapsed;
                        if (remaining <= 0)
                        {
                            // Timer is ready to fire.

                            if (timer._period != Timeout.UnsignedInfinite)
                            {
                                // This is a repeating timer; schedule it to run again.

                                // Discount the extra amount of time that has elapsed since the previous firing time to
                                // prevent timer ticks from drifting.  If enough time has already elapsed for the timer to fire
                                // again, meaning the timer can't keep up with the short period, have it fire 1 ms from now to
                                // avoid spinning without a delay.
                                timer._startTicks = nowTicks;
                                long elapsedForNextDueTime = elapsed - timer._dueTime;
                                timer._dueTime = (elapsedForNextDueTime < timer._period) ?
                                                 timer._period - (uint)elapsedForNextDueTime :
                                                 1;

                                // Update the timer if this becomes the next timer to fire.
                                if (timer._dueTime < nextTimerDuration)
                                {
                                    haveTimerToSchedule = true;
                                    nextTimerDuration   = timer._dueTime;
                                }

                                // Validate that the repeating timer is still on the right list.  It's likely that
                                // it started in the long list and was moved to the short list at some point, so
                                // we now want to move it back to the long list if that's where it belongs. Note that
                                // if we're currently processing the short list and move it to the long list, we may
                                // end up revisiting it again if we also enumerate the long list, but we will have already
                                // updated the due time appropriately so that we won't fire it again (it's also possible
                                // but rare that we could be moving a timer from the long list to the short list here,
                                // if the initial due time was set to be long but the timer then had a short period).
                                bool targetShortList = (nowTicks + timer._dueTime) - _currentAbsoluteThreshold <= 0;
                                if (timer._short != targetShortList)
                                {
                                    MoveTimerToCorrectList(timer, targetShortList);
                                }
                            }
                            else
                            {
                                // Not repeating; remove it from the queue
                                DeleteTimer(timer);
                            }

                            // If this is the first timer, we'll fire it on this thread (after processing
                            // all others). Otherwise, queue it to the ThreadPool.
                            if (timerToFireOnThisThread == null)
                            {
                                timerToFireOnThisThread = timer;
                            }
                            else
                            {
                                ThreadPool.UnsafeQueueUserWorkItemInternal(timer, preferLocal: false);
                            }
                        }
                        else
                        {
                            // This timer isn't ready to fire.  Update the next time the native timer fires if necessary,
                            // and move this timer to the short list if its remaining time is now at or under the threshold.

                            if (remaining < nextTimerDuration)
                            {
                                haveTimerToSchedule = true;
                                nextTimerDuration   = (uint)remaining;
                            }

                            if (!timer._short && remaining <= ShortTimersThresholdMilliseconds)
                            {
                                MoveTimerToCorrectList(timer, shortList: true);
                            }
                        }

                        timer = next;
                    }

                    // Switch to process the long list if necessary.
                    if (listNum == 0)
                    {
                        // Determine how much time remains between now and the current threshold.  If time remains,
                        // we can skip processing the long list.  We use > rather than >= because, although we
                        // know that if remaining == 0 no timers in the long list will need to be fired, we
                        // don't know without looking at them when we'll need to call FireNextTimers again.  We
                        // could in that case just set the next firing to 1, but we may as well just iterate the
                        // long list now; otherwise, most timers created in the interim would end up in the long
                        // list and we'd likely end up paying for another invocation of FireNextTimers that could
                        // have been delayed longer (to whatever is the current minimum in the long list).
                        long remaining = _currentAbsoluteThreshold - nowTicks;
                        if (remaining > 0)
                        {
                            if (_shortTimers == null && _longTimers != null)
                            {
                                // We don't have any short timers left and we haven't examined the long list,
                                // which means we likely don't have an accurate nextTimerDuration.
                                // But we do know that nothing in the long list will be firing before or at _currentAbsoluteThreshold,
                                // so we can just set nextTimerDuration to the difference between then and now.
                                nextTimerDuration   = (uint)remaining + 1;
                                haveTimerToSchedule = true;
                            }
                            break;
                        }

                        // Switch to processing the long list.
                        timer = _longTimers;

                        // Now that we're going to process the long list, update the current threshold.
                        _currentAbsoluteThreshold = nowTicks + ShortTimersThresholdMilliseconds;
                    }
                }

                // If we still have scheduled timers, update the timer to ensure it fires
                // in time for the next one in line.
                if (haveTimerToSchedule)
                {
                    EnsureTimerFiresBy(nextTimerDuration);
                }
            }

            // Fire the user timer outside of the lock!
            timerToFireOnThisThread?.Fire();
        }