File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Threading\TimerQueue.Portable.cs
Web Access
Project: System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Threading
{
    //
    // Portable implementation of Timer
    //
    internal sealed partial class TimerQueue : IThreadPoolWorkItem
    {
        private static List<TimerQueue>? s_scheduledTimers;
        private static List<TimerQueue>? s_scheduledTimersToFire;

        /// <summary>
        /// This event is used by the timer thread to wait for timer expiration. It is also
        /// used to notify the timer thread that a new timer has been set.
        /// </summary>
        private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false);

        private static readonly Lock s_timerEventLock = new Lock();

        // this means that it's in the s_scheduledTimers collection, not that it's the one which would run on the next TimeoutCallback
        private bool _isScheduled;
        private long _scheduledDueTimeMs;

        private static List<TimerQueue> InitializeScheduledTimerManager_Locked()
        {
            Debug.Assert(s_scheduledTimers == null);

            var timers = new List<TimerQueue>(Instances.Length);
            s_scheduledTimersToFire ??= new List<TimerQueue>(Instances.Length);

            // The timer thread must start in the default execution context without transferring the context, so
            // using UnsafeStart() instead of Start()
            Thread timerThread = new Thread(TimerThread)
            {
                Name = ".NET Timer",
                IsBackground = true
            };
            timerThread.UnsafeStart();

            // Do this after creating the thread in case thread creation fails so that it will try again next time
            s_scheduledTimers = timers;
            return timers;
        }

        private bool SetTimerPortable(uint actualDuration)
        {
            Debug.Assert((int)actualDuration >= 0);
            long dueTimeMs = Environment.TickCount64 + (int)actualDuration;
            AutoResetEvent timerEvent = s_timerEvent;
            Lock timerEventLock = s_timerEventLock;

            lock (timerEventLock)
            {
                if (!_isScheduled)
                {
                    List<TimerQueue> timers = s_scheduledTimers ?? InitializeScheduledTimerManager_Locked();

                    timers.Add(this);
                    _isScheduled = true;
                }

                _scheduledDueTimeMs = dueTimeMs;
            }

            timerEvent.Set();
            return true;
        }

        /// <summary>
        /// This method is executed on a dedicated 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;
            Lock timerEventLock = s_timerEventLock;
            List<TimerQueue> timersToFire = s_scheduledTimersToFire!;
            List<TimerQueue> timers;

            lock (timerEventLock)
            {
                timers = s_scheduledTimers!;
            }

            int shortestWaitDurationMs = Timeout.Infinite;
            while (true)
            {
                timerEvent.WaitOne(shortestWaitDurationMs);

                long currentTimeMs = Environment.TickCount64;
                shortestWaitDurationMs = int.MaxValue;
                lock (timerEventLock)
                {
                    for (int i = timers.Count - 1; i >= 0; --i)
                    {
                        TimerQueue timer = timers[i];
                        long 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 = (int)waitDurationMs;
                        }
                    }
                }

                if (timersToFire.Count > 0)
                {
                    foreach (TimerQueue timerToFire in timersToFire)
                    {
                        ThreadPool.UnsafeQueueHighPriorityWorkItemInternal(timerToFire);
                    }
                    timersToFire.Clear();
                }

                if (shortestWaitDurationMs == int.MaxValue)
                {
                    shortestWaitDurationMs = Timeout.Infinite;
                }
            }
        }

        void IThreadPoolWorkItem.Execute() => FireNextTimers();
    }
}