提交 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 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics;
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>
/// 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 AutoResetEvent _timerEvent;
private static readonly AutoResetEvent s_timerEvent = new AutoResetEvent(false);
/// <summary>
/// This field stores the value of next timer that the timer thread should install.
/// </summary>
private volatile int _nextTimerDuration;
private bool _isScheduled;
private int _scheduledDueTimeMs;
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.
// The TimerQueue code ensures that timer duration is not greater than max Int32 value
Debug.Assert(actualDuration <= (uint)int.MaxValue);
_nextTimerDuration = (int)actualDuration;
// 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)
Debug.Assert(s_scheduledTimers == null);
var timers = new List<TimerQueue>(Instances.Length);
if (s_scheduledTimersToFire == null)
{
_timerEvent = new AutoResetEvent(false);
Thread thread = new Thread(TimerThread);
thread.IsBackground = true; // Keep this thread from blocking process shutdown
thread.Start();
s_scheduledTimersToFire = new List<TimerQueue>(Instances.Length);
}
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;
}
/// <summary>
/// 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>
private void TimerThread()
private static void TimerThread()
{
// Get wait time for the next timer
int currentTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite);
AutoResetEvent timerEvent = s_timerEvent;
List<TimerQueue> timersToFire = s_scheduledTimersToFire;
List<TimerQueue> timers;
lock (timerEvent)
{
timers = s_scheduledTimers;
}
int shortestWaitDurationMs = Timeout.Infinite;
while (true)
{
// Wait for the current timer to expire.
// 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 startWait = TickCount;
bool timerHasExpired = !_timerEvent.WaitOne(currentTimerInterval);
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)
timerEvent.WaitOne(shortestWaitDurationMs);
int currentTimeMs = TickCount;
shortestWaitDurationMs = int.MaxValue;
lock (timerEvent)
{
if (elapsedTime >= currentTimerInterval)
{
timerHasExpired = true;
}
else
for (int i = timers.Count - 1; i >= 0; --i)
{
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 (timerHasExpired)
if (timersToFire.Count > 0)
{
FireNextTimers();
// When FireNextTimers() installs a new timer, it also sets the timer event.
// Reset the event so the timer thread is not woken up right away unnecessary.
_timerEvent.Reset();
currentTimerInterval = Timeout.Infinite;
foreach (TimerQueue timerToFire in timersToFire)
{
ThreadPool.UnsafeQueueUserWorkItemInternal(timerToFire, preferLocal: false);
}
timersToFire.Clear();
}
int nextTimerInterval = Interlocked.Exchange(ref _nextTimerDuration, Timeout.Infinite);
if (nextTimerInterval != Timeout.Infinite)
if (shortestWaitDurationMs == int.MaxValue)
{
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.
先完成此消息的编辑!
想要评论请 注册