提交 ef4d5393 编写于 作者: K Koundinya Veluri 提交者: Jan Kotas

Change Timer implementation on Unixes to use only one scheduling thread (dotnet/coreclr#7071)

* Change Timer implementation on Unixes to use only one scheduling thread

- Separated from https://github.com/dotnet/corert/pull/7066

* Address feedback from https://github.com/dotnet/corert/pull/7066

* Remove reference to s_lock

* Reduce work inside lock

* Move _id

* Fix duplicate timers in scheduled timer list, move info to TimerQueue
Signed-off-by: Ndotnet-bot <dotnet-bot@microsoft.com>


Commit migrated from https://github.com/dotnet/coreclr/commit/6e215e1faa8275c1468bcd059e29a530d2de0064
上级 e5a94ff5
...@@ -2,104 +2,141 @@ ...@@ -2,104 +2,141 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
namespace System.Threading namespace System.Threading
{ {
internal partial class TimerQueue //
// Unix-specific implementation of Timer
//
internal partial class TimerQueue : IThreadPoolWorkItem
{ {
private static List<TimerQueue> s_scheduledTimers;
private static List<TimerQueue> s_scheduledTimersToFire;
/// <summary> /// <summary>
/// This event is used by the timer thread to wait for timer expiration. It is also /// 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. /// used to notify the timer thread that a new timer has been set.
/// </summary> /// </summary>
private AutoResetEvent _timerEvent; private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false);
/// <summary> private bool _isScheduled;
/// This field stores the value of next timer that the timer thread should install. private int _scheduledDueTimeMs;
/// </summary>
private volatile int _nextTimerDuration;
private TimerQueue(int id) private TimerQueue(int id)
{ {
} }
private bool SetTimer(uint actualDuration) private static List<TimerQueue> InitializeScheduledTimerManager_Locked()
{ {
// Note: AutoResetEvent.WaitOne takes an Int32 value as a timeout. Debug.Assert(s_scheduledTimers == null);
// The TimerQueue code ensures that timer duration is not greater than max Int32 value
Debug.Assert(actualDuration <= (uint)int.MaxValue); var timers = new List<TimerQueue>(Instances.Length);
_nextTimerDuration = (int)actualDuration; if (s_scheduledTimersToFire == null)
// If this is the first time the timer is set then we need to create a thread that
// will manage and respond to timer requests. Otherwise, simply signal the timer thread
// to notify it that the timer duration has changed.
if (_timerEvent == null)
{ {
_timerEvent = new AutoResetEvent(false); s_scheduledTimersToFire = new List<TimerQueue>(Instances.Length);
Thread thread = new Thread(TimerThread);
thread.IsBackground = true; // Keep this thread from blocking process shutdown
thread.Start();
} }
else
Thread timerThread = new Thread(TimerThread);
timerThread.IsBackground = true;
timerThread.Start();
// 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 SetTimer(uint actualDuration)
{
Debug.Assert((int)actualDuration >= 0);
int dueTimeMs = TickCount + (int)actualDuration;
AutoResetEvent timerEvent = s_timerEvent;
lock (timerEvent)
{ {
_timerEvent.Set(); if (!_isScheduled)
{
List<TimerQueue> timers = s_scheduledTimers;
if (timers == null)
{
timers = InitializeScheduledTimerManager_Locked();
}
timers.Add(this);
_isScheduled = true;
}
_scheduledDueTimeMs = dueTimeMs;
} }
timerEvent.Set();
return true; return true;
} }
/// <summary> /// <summary>
/// This method is executed on a dedicated a timer thread. Its purpose is /// This method is executed on a dedicated a timer thread. Its purpose is
/// to handle timer request and notify the TimerQueue when a timer expires. /// to handle timer requests and notify the TimerQueue when a timer expires.
/// </summary> /// </summary>
private void TimerThread() private static void TimerThread()
{ {
// Get wait time for the next timer AutoResetEvent timerEvent = s_timerEvent;
int currentTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite); List<TimerQueue> timersToFire = s_scheduledTimersToFire;
List<TimerQueue> timers;
lock (timerEvent)
{
timers = s_scheduledTimers;
}
int shortestWaitDurationMs = Timeout.Infinite;
while (true) while (true)
{ {
// Wait for the current timer to expire. timerEvent.WaitOne(shortestWaitDurationMs);
// We will be woken up because either 1) the wait times out, which will indicate that
// the current timer has expired and/or 2) the TimerQueue installs a new (earlier) timer. int currentTimeMs = TickCount;
int startWait = TickCount; shortestWaitDurationMs = int.MaxValue;
bool timerHasExpired = !_timerEvent.WaitOne(currentTimerInterval); lock (timerEvent)
uint elapsedTime = (uint)(TickCount - startWait);
// The timer event can be set after this thread reads the new timer interval but before it enters
// the wait state. This can cause a spurious wake up. In addition, expiration of current timer can
// happen almost at the same time as this thread is signaled to install a new timer. To handle
// these cases, we need to update the current interval based on the elapsed time.
if (currentTimerInterval != Timeout.Infinite)
{ {
if (elapsedTime >= currentTimerInterval) for (int i = timers.Count - 1; i >= 0; --i)
{
timerHasExpired = true;
}
else
{ {
currentTimerInterval -= (int)elapsedTime; 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;
}
} }
} }
// Check whether TimerQueue needs to process expired timers. if (timersToFire.Count > 0)
if (timerHasExpired)
{ {
FireNextTimers(); foreach (TimerQueue timerToFire in timersToFire)
{
// When FireNextTimers() installs a new timer, it also sets the timer event. ThreadPool.UnsafeQueueUserWorkItemInternal(timerToFire, preferLocal: false);
// Reset the event so the timer thread is not woken up right away unnecessary. }
_timerEvent.Reset(); timersToFire.Clear();
currentTimerInterval = Timeout.Infinite;
} }
int nextTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite); if (shortestWaitDurationMs == int.MaxValue)
if (nextTimerInterval != Timeout.Infinite)
{ {
currentTimerInterval = nextTimerInterval; shortestWaitDurationMs = Timeout.Infinite;
} }
} }
} }
void IThreadPoolWorkItem.Execute() => FireNextTimers();
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册