/// <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; } } }
// 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(); }