Skip to content

Commit

Permalink
[feat]新增TimeProvider和ITimer,支持net8以下环境。调度器支持设置TimeProvider,以支持自定义时间,方…
Browse files Browse the repository at this point in the history
…便注入星尘时间提供者,使用AppClient.GetNow的服务器时间。
  • Loading branch information
nnhy committed Jul 18, 2024
1 parent 5a55467 commit dc75048
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 27 deletions.
69 changes: 69 additions & 0 deletions NewLife.Core/Common/TimeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Diagnostics;

namespace System;

#if NETFRAMEWORK || NETSTANDARD || NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0
/// <summary>提供时间的抽象</summary>
public abstract class TimeProvider
{
private sealed class SystemTimeProvider : TimeProvider
{
internal SystemTimeProvider() { }
}

private static readonly Int64 s_minDateTicks = DateTime.MinValue.Ticks;

private static readonly Int64 s_maxDateTicks = DateTime.MaxValue.Ticks;

/// <summary>获取一个 TimeProvider ,它提供基于 UtcNow的时钟、基于 的 Local时区、基于 的 Stopwatch高性能时间戳和基于 的 Timer计时器。</summary>
public static TimeProvider System { get; set; } = new SystemTimeProvider();

/// <summary>根据此 TimeProvider的时间概念获取本地时区。</summary>
public virtual TimeZoneInfo LocalTimeZone => TimeZoneInfo.Local;

/// <summary>获取 的频率 GetTimestamp() 作为每秒时钟周期数。</summary>
public virtual Int64 TimestampFrequency => Stopwatch.Frequency;

/// <summary>根据此 TimeProvider的时间概念,获取当前协调世界时 (UTC) 日期和时间,偏移量为零。</summary>
/// <returns></returns>
public virtual DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow;

/// <summary>根据基于 TimeProvider的时间概念 GetUtcNow()获取当前日期和时间,偏移量设置为 LocalTimeZone与协调世界时 (UTC) 的偏移量。</summary>
/// <returns></returns>
public DateTimeOffset GetLocalNow()
{
var utcNow = GetUtcNow();
var localTimeZone = LocalTimeZone ?? throw new ArgumentNullException(nameof(LocalTimeZone));

var utcOffset = localTimeZone.GetUtcOffset(utcNow);
if (utcOffset.Ticks == 0L) return utcNow;

var num = utcNow.Ticks + utcOffset.Ticks;
if ((UInt64)num > (UInt64)s_maxDateTicks)
num = ((num < s_minDateTicks) ? s_minDateTicks : s_maxDateTicks);

return new DateTimeOffset(num, utcOffset);
}

/// <summary>获取当前高频值,该值旨在测量计时器机制中精度较高的小时间间隔。</summary>
/// <returns></returns>
public virtual Int64 GetTimestamp() => Stopwatch.GetTimestamp();

/// <summary>获取使用 GetTimestamp()检索到的两个时间戳之间的已用时间。</summary>
/// <param name="startingTimestamp"></param>
/// <param name="endingTimestamp"></param>
/// <returns></returns>
public TimeSpan GetElapsedTime(Int64 startingTimestamp, Int64 endingTimestamp)
{
var timestampFrequency = TimestampFrequency;
if (timestampFrequency <= 0) throw new ArgumentOutOfRangeException(nameof(TimestampFrequency));

return new TimeSpan((Int64)((endingTimestamp - startingTimestamp) * (10000000.0 / timestampFrequency)));
}

/// <summary>获取自使用 GetTimestamp()检索值以来startingTimestamp的运行时间。</summary>
/// <param name="startingTimestamp"></param>
/// <returns></returns>
public TimeSpan GetElapsedTime(Int64 startingTimestamp) => GetElapsedTime(startingTimestamp, GetTimestamp());
}
#endif
13 changes: 13 additions & 0 deletions NewLife.Core/Threading/ITimer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace System.Threading;

#if NETFRAMEWORK || NETSTANDARD || NETCOREAPP3_1 || NET5_0 || NET6_0 || NET7_0
/// <summary>表示可以更改其到期时间和时间段的计时器。</summary>
public interface ITimer : IDisposable
{
/// <summary>更改计时器的启动时间和方法调用之间的时间间隔,使用 TimeSpan 值度量时间间隔。</summary>
/// <param name="dueTime">一个 TimeSpan,表示在调用构造 ITimer 时指定的回调方法之前的延迟时间量。 指定 InfiniteTimeSpan 可防止重新启动计时器。 指定 Zero 可立即重新启动计时器。</param>
/// <param name="period">构造 Timer 时指定的回调方法调用之间的时间间隔。 指定 InfiniteTimeSpan 可以禁用定期终止。</param>
/// <returns></returns>
Boolean Change(TimeSpan dueTime, TimeSpan period);
}
#endif
27 changes: 12 additions & 15 deletions NewLife.Core/Threading/TimerScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using NewLife.Log;
using NewLife.Reflection;

#nullable enable
namespace NewLife.Threading;

/// <summary>定时器调度器</summary>
Expand Down Expand Up @@ -37,6 +36,9 @@ public static TimerScheduler Create(String name)
private static TimerScheduler? _Current;
/// <summary>当前调度器</summary>
public static TimerScheduler? Current { get => _Current; private set => _Current = value; }

/// <summary>全局时间提供者。影响所有调度器</summary>
public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System;
#endregion

#region 属性
Expand All @@ -49,10 +51,13 @@ public static TimerScheduler Create(String name)
/// <summary>最大耗时。超过时报警告日志,默认500ms</summary>
public Int32 MaxCost { get; set; } = 500;

/// <summary>时间提供者。该调度器下所有绝对定时器,均从此获取当前时间</summary>
public TimeProvider? TimeProvider { get; set; }

private Thread? thread;
private Int32 _tid;

private TimerX[] Timers = new TimerX[0];
private TimerX[] Timers = [];
#endregion

/// <summary>把定时器加入队列</summary>
Expand Down Expand Up @@ -163,9 +168,6 @@ private void Process(Object? state)
{
if (!timer.Calling && CheckTime(timer, now))
{
//// 是否能够执行
//if (timer.CanExecute == null || timer.CanExecute())
//{
// 必须在主线程设置状态,否则可能异步线程还没来得及设置开始状态,主线程又开始了新的一轮调度
timer.Calling = true;
if (timer.IsAsyncTask)
Expand All @@ -186,14 +188,6 @@ private void Process(Object? state)
XTrace.WriteException(ex);
}
}, timer);
// 内部线程池,让异步任务有公平竞争CPU的机会
//ThreadPoolX.QueueUserWorkItem(Execute, timer);
//}
//// 即使不能执行,也要设置下一次的时间
//else
//{
// OnFinish(timer);
//}
}
}
}
Expand Down Expand Up @@ -361,15 +355,18 @@ private void OnFinish(TimerX timer)
_period = p;
}

/// <summary>获取当前时间。该调度器下所有绝对定时器,均从此获取当前时间</summary>
/// <returns></returns>
public DateTime GetNow() => (TimeProvider ?? GlobalTimeProvider).GetUtcNow().LocalDateTime;

/// <summary>已重载。</summary>
/// <returns></returns>
public override String ToString() => Name;

#region 设置
#region 日志
/// <summary>日志</summary>
public ILog Log { get; set; } = Logger.Null;

private void WriteLog(String format, params Object?[] args) => Log?.Info(Name + format, args);
#endregion
}
#nullable restore
60 changes: 48 additions & 12 deletions NewLife.Core/Threading/TimerX.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Reflection;
using NewLife.Log;

#nullable enable
namespace NewLife.Threading;

/// <summary>不可重入的定时器,支持Cron</summary>
Expand All @@ -17,7 +16,7 @@ namespace NewLife.Threading;
///
/// TimerX必须维持对象,否则Scheduler也没有维持对象时,大家很容易一起被GC回收。
/// </remarks>
public class TimerX : IDisposable
public class TimerX : ITimer, IDisposable
{
#region 属性
/// <summary>编号</summary>
Expand Down Expand Up @@ -116,10 +115,11 @@ private TimerX(Object? target, MethodInfo method, Object? state, String? schedul

// 使用开机滴答作为定时调度基准
_nextTick = Runtime.TickCount64;
_baseTime = DateTime.Now.AddMilliseconds(-_nextTick);
//_baseTime = DateTime.Now.AddMilliseconds(-_nextTick);

Scheduler = (scheduler == null || scheduler.IsNullOrEmpty()) ? TimerScheduler.Default : TimerScheduler.Create(scheduler);
//Scheduler.Add(this);
_baseTime = Scheduler.GetNow().AddMilliseconds(-_nextTick);

TracerName = $"timer:{method.Name}";
}
Expand Down Expand Up @@ -180,7 +180,8 @@ public TimerX(TimerCallback callback, Object? state, DateTime startTime, Int32 p
Period = period;
Absolutely = true;

var now = DateTime.Now;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = startTime;
while (next < now) next = next.AddMilliseconds(period);

Expand All @@ -206,7 +207,8 @@ public TimerX(Func<Object, Task> callback, Object? state, DateTime startTime, In
Period = period;
Absolutely = true;

var now = DateTime.Now;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = startTime;
while (next < now) next = next.AddMilliseconds(period);

Expand Down Expand Up @@ -237,7 +239,8 @@ public TimerX(TimerCallback callback, Object? state, String cronExpression, Stri

Absolutely = true;

var now = DateTime.Now;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Expand Down Expand Up @@ -269,7 +272,8 @@ public TimerX(Func<Object, Task> callback, Object? state, String cronExpression,
Async = true;
Absolutely = true;

var now = DateTime.Now;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Expand Down Expand Up @@ -298,6 +302,16 @@ protected virtual void Dispose(Boolean disposing)
// 释放非托管资源
Scheduler?.Remove(this, disposing ? "Dispose" : "GC");
}

#if NET5_0_OR_GREATER
/// <summary>异步销毁</summary>
/// <returns></returns>
public ValueTask DisposeAsync()
{
Dispose();
return ValueTask.CompletedTask;
}
#endif
#endregion

#region 方法
Expand All @@ -308,7 +322,7 @@ private void SetNextTick(Int64 ms)
{
// 使用开机滴答来做定时调度,无惧时间回拨,每次修正时间基准
var tick = Runtime.TickCount64;
_baseTime = DateTime.Now.AddMilliseconds(-tick);
_baseTime = Scheduler.GetNow().AddMilliseconds(-tick);
_nextTick = tick + ms;
}

Expand Down Expand Up @@ -342,7 +356,8 @@ internal Int32 SetAndGetNextTime()
{
// Cron以当前时间开始计算下一次
// 绝对时间还没有到时,不计算下一次
var now = DateTime.Now;
//var now = DateTime.Now;
var now = Scheduler.GetNow();
DateTime next;
if (_crons != null)
{
Expand Down Expand Up @@ -373,6 +388,28 @@ internal Int32 SetAndGetNextTime()
return period;
}
}

/// <summary>更改计时器的启动时间和方法调用之间的时间间隔,使用 TimeSpan 值度量时间间隔。</summary>
/// <param name="dueTime">一个 TimeSpan,表示在调用构造 ITimer 时指定的回调方法之前的延迟时间量。 指定 InfiniteTimeSpan 可防止重新启动计时器。 指定 Zero 可立即重新启动计时器。</param>
/// <param name="period">构造 Timer 时指定的回调方法调用之间的时间间隔。 指定 InfiniteTimeSpan 可以禁用定期终止。</param>
/// <returns></returns>
public Boolean Change(TimeSpan dueTime, TimeSpan period)
{
if (Absolutely) return false;
if (Crons != null && Crons.Length > 0) return false;

if (period.TotalMilliseconds <= 0)
{
Dispose();
return true;
}

Period = (Int32)period.TotalMilliseconds;

if (dueTime.TotalMilliseconds >= 0) SetNext((Int32)dueTime.TotalMilliseconds);

return true;
}
#endregion

#region 静态方法
Expand All @@ -396,7 +433,7 @@ public static DateTime Now
if (_NowTimer == null)
{
// 多线程下首次访问Now可能取得空时间
_Now = DateTime.Now;
_Now = TimerScheduler.Default.GetNow();

_NowTimer = new TimerX(CopyNow, null, 0, 500);
}
Expand All @@ -407,7 +444,7 @@ public static DateTime Now
}
}

private static void CopyNow(Object? state) => _Now = DateTime.Now;
private static void CopyNow(Object? state) => _Now = TimerScheduler.Default.GetNow();
#endregion

#region 辅助
Expand All @@ -416,4 +453,3 @@ public static DateTime Now
public override String ToString() => $"[{Id}]{Method.DeclaringType?.Name}.{Method.Name} ({(_crons != null ? _crons.Join(";") : (Period + "ms"))})";
#endregion
}
#nullable restore

0 comments on commit dc75048

Please sign in to comment.