Skip to content

Commit

Permalink
[feat]TimerX支持多个Cron表达式,以支持多场景定时任务
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhy committed Mar 23, 2024
1 parent 28606fd commit d622277
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 127 deletions.
17 changes: 9 additions & 8 deletions NewLife.Core/Threading/Cron.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using System.Diagnostics.CodeAnalysis;

namespace NewLife.Threading;
namespace NewLife.Threading;

/// <summary>轻量级Cron表达式</summary>
/// <remarks>
/// 基本构成:秒+分+时+天+月+星期
/// 基本构成:秒+分+时+天+月+星期+年
/// 每段构成:
/// * 所有可能的值,该类型片段全部可选
/// , 列出枚举值
/// - 范围,横杠表示的一个区间可选
/// / 指定数值的增量,在上述可选数字内,间隔多少选一个
/// ? 不指定值,仅日期和星期域支持该字符
/// # 确定每个月第几个星期几,仅星期域支持该字符
/// # 确定每个月第几个星期几,L表示倒数,仅星期域支持该字符
/// 数字,具体某个数值可选
/// 逗号多选,逗号分隔的多个数字或区间可选
/// </remarks>
Expand All @@ -22,10 +20,13 @@ namespace NewLife.Threading;
/// * 1-10,13,25/3 * * * 每小时的1分4分7分10分13分25分,每一秒各一次
/// 0 0 0 1 * * 每个月1日的0点整
/// 0 0 2 * * 1-5 每个工作日的凌晨2点
/// 0 0 0 ? ? 1-7#1 每月第一周的任意一天(周一~周日)的0点整
/// 0 0 0 ? ? 3-5#L2 每个月倒数第二个星期三到星期五的0点整
///
/// 星期部分采用Linux和.NET风格,0表示周日,1表示周一。
/// 可设置Sunday为1,1表示周日,2表示周一。
///
/// 文档 https://newlifex.com/core/cron
/// 参考文档 https://help.aliyun.com/document_detail/64769.html
/// </example>
public class Cron
Expand Down Expand Up @@ -129,9 +130,9 @@ public Boolean Parse(String expression)
if (!TryParse(ss.Length > 4 ? ss[4] : "*", 1, 13, out vs)) return false;
Months = vs;

var dic = new Dictionary<Int32, Int32>();
if (!TryParseWeek(ss.Length > 5 ? ss[5] : "*", 0, 7, dic)) return false;
DaysOfWeek = dic;
var weeks = new Dictionary<Int32, Int32>();
if (!TryParseWeek(ss.Length > 5 ? ss[5] : "*", 0, 7, weeks)) return false;
DaysOfWeek = weeks;

_expression = expression;

Expand Down
46 changes: 32 additions & 14 deletions NewLife.Core/Threading/TimerX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ public Object? State
public Func<Boolean>? CanExecute { get; set; }

/// <summary>Cron表达式,实现复杂的定时逻辑</summary>
public Cron? Cron => _cron;
public Cron[]? Crons => _crons;

/// <summary>Cron表达式,实现复杂的定时逻辑</summary>
[Obsolete("=>Crons")]
public Cron? Cron => _crons?.FirstOrDefault();

/// <summary>链路追踪。追踪每一次定时事件</summary>
public ITracer? Tracer { get; set; }
Expand All @@ -90,7 +94,7 @@ public Object? State
public String TracerName { get; set; }

private DateTime _AbsolutelyNext;
private readonly Cron? _cron;
private readonly Cron[]? _crons;
#endregion

#region 静态
Expand Down Expand Up @@ -214,20 +218,27 @@ public TimerX(Func<Object, Task> callback, Object? state, DateTime startTime, In
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="cronExpression">Cron表达式</param>
/// <param name="cronExpression">Cron表达式。支持多个表达式,分号分隔</param>
/// <param name="scheduler">调度器</param>
public TimerX(TimerCallback callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression));

_cron = new Cron();
if (!_cron.Parse(cronExpression)) throw new ArgumentException("Invalid Cron expression", nameof(cronExpression));
var list = new List<Cron>();
foreach (var item in cronExpression.Split(";"))
{
var cron = new Cron();
if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression));

list.Add(cron);
}
_crons = list.ToArray();

Absolutely = true;

var now = DateTime.Now;
var next = _cron.GetNext(now);
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
Expand All @@ -237,22 +248,29 @@ public TimerX(TimerCallback callback, Object? state, String cronExpression, Stri
/// <summary>实例化一个Cron定时器</summary>
/// <param name="callback">委托</param>
/// <param name="state">用户数据</param>
/// <param name="cronExpression">Cron表达式</param>
/// <param name="cronExpression">Cron表达式。支持多个表达式,分号分隔</param>
/// <param name="scheduler">调度器</param>
public TimerX(Func<Object, Task> callback, Object? state, String cronExpression, String? scheduler = null) : this(callback.Target, callback.Method, state, scheduler)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (cronExpression.IsNullOrEmpty()) throw new ArgumentNullException(nameof(cronExpression));

_cron = new Cron();
if (!_cron.Parse(cronExpression)) throw new ArgumentException("Invalid Cron expression", nameof(cronExpression));
var list = new List<Cron>();
foreach (var item in cronExpression.Split(";"))
{
var cron = new Cron();
if (!cron.Parse(item)) throw new ArgumentException($"Invalid Cron expression[{item}]", nameof(cronExpression));

list.Add(cron);
}
_crons = list.ToArray();

IsAsyncTask = true;
Async = true;
Absolutely = true;

var now = DateTime.Now;
var next = _cron.GetNext(now);
var next = _crons.Min(e => e.GetNext(now));
var ms = (Int64)(next - now).TotalMilliseconds;
_AbsolutelyNext = next;
Init(ms);
Expand Down Expand Up @@ -326,12 +344,12 @@ internal Int32 SetAndGetNextTime()
// 绝对时间还没有到时,不计算下一次
var now = DateTime.Now;
DateTime next;
if (_cron != null)
if (_crons != null)
{
next = _cron.GetNext(now);
next = _crons.Min(e => e.GetNext(now));

// 如果cron计算得到的下一次时间过近,则需要重新计算
if ((next - now).TotalMilliseconds < 1000) next = _cron.GetNext(next);
if ((next - now).TotalMilliseconds < 1000) next = _crons.Min(e => e.GetNext(next));
}
else
{
Expand Down Expand Up @@ -395,7 +413,7 @@ public static DateTime Now
#region 辅助
/// <summary>已重载</summary>
/// <returns></returns>
public override String ToString() => $"[{Id}]{Method.DeclaringType?.Name}.{Method.Name} ({(_cron != null ? _cron.ToString() : (Period + "ms"))})";
public override String ToString() => $"[{Id}]{Method.DeclaringType?.Name}.{Method.Name} ({(_crons != null ? _crons.Join(";") : (Period + "ms"))})";
#endregion
}
#nullable restore
6 changes: 3 additions & 3 deletions XUnitTest.Core/Net/ISocketRemoteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public class ISocketRemoteTests
{
private FileInfo GetFile()
{
var src = "D:\\Tools".AsDirectory().GetFiles().OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "../../".AsDirectory().GetFiles().OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "data/".AsDirectory().GetFiles().OrderByDescending(e => e.Length).FirstOrDefault();
var src = "D:\\Tools".AsDirectory().GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "../../".AsDirectory().GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "data/".AsDirectory().GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
XTrace.WriteLine("发送文件:{0}", src.FullName);
XTrace.WriteLine("文件大小:{0}", src.Length.ToGMK());

Expand Down
Loading

0 comments on commit d622277

Please sign in to comment.