using System; using System.Collections.Generic; using System.ComponentModel.Design; using System.Reflection; using System.Text; using System.Threading; namespace Apewer { /// Cron 特性,默认间隔为 60 秒。 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class CronAttribute : Attribute, IToJson { #region attribute private int _mode = 0; private int _seconds = 0; private CronCycle _cycle = CronCycle.Once; /// 获取秒数。 public int Seconds { get { return _seconds; } } /// 创建 Cron 特性,可指定两次 Cron 执行的间隔秒数。 public CronAttribute(int seconds = 60) { _mode = 1; _seconds = seconds; if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds)); } /// 创建 Cron 特性,在每个周期中延迟指定秒数后执行。 /// public CronAttribute(CronCycle cycle, int seconds = 0) { _mode = 2; _cycle = cycle; _seconds = seconds; if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds)); } /// 生成 Json 对象。 public Json ToJson() { var mode = _mode == 1 ? "interval" : "cycle"; var type = _type == null ? null : _type.FullName; var json = Json.NewObject(); json.SetProperty("type", type); json.SetProperty("mode", mode); if (_mode == 2) json.SetProperty("cycle", _cycle.ToString().ToLower()); json.SetProperty("second", _seconds); return json; } /// public override string ToString() { if (_type != null) { if (_mode == 1) return $"Type = {_type.FullName}, Seconds = {_seconds}"; if (_mode == 2) return $"Type = {_type.FullName}, Cycle = {_cycle}, Seconds = {_seconds}"; } return base.ToString(); } #endregion #region payload /// 执行 Cron 的类。 public Type Type { get => _type; } // 传入。 Type _type = null; Logger _logger = null; bool _event = false; // 实时。 Thread _thread = null; bool _alive = false; bool _break = false; /// 运行当前 Cron。 void Run() { if (_alive) return; if (_type == null) return; _break = false; UsePool(pool => { if (pool.ContainsKey(_type)) return; pool.Add(_type, this); _alive = true; _thread = new Thread(Payload); _thread.IsBackground = false; _thread.Start(); }); } // 当前进程睡眠。 static void Sleep() => Thread.Sleep(500); // 线程负载。 void Payload() { // 基于间隔时间调用。 if (_mode == 1) { // 初始值为最小值,保证第一次启动时必定运行。 var next = DateTime.MinValue; while (true) { if (_break || _break_all) break; if (_now < next) { Sleep(); if (_break || _break_all) break; continue; } Invoke(); next = DateTime.Now.AddSeconds(_seconds); } } // 仅执行一次,延迟启动。 if (_mode == 2 && _cycle == CronCycle.Once) { var next = DateTime.Now.AddSeconds(_seconds); while (_now < next) { if (_break || _break_all) break; Sleep(); if (_break || _break_all) break; } Invoke(); } // 基于周期调用。 if (_mode == 2 && _cycle != CronCycle.Once) { // 计算当前期间的运行时间。 DateTime next = DateTime.Now; switch (_cycle) { case CronCycle.Minute: next = new DateTime(next.Year, next.Month, next.Day, next.Hour, next.Minute, 0, 0); break; case CronCycle.Hour: next = new DateTime(next.Year, next.Month, next.Day, next.Hour, 0, 0, 0); break; case CronCycle.Day: next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0); break; case CronCycle.Week: next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0).AddDays(-(int)next.DayOfWeek); break; } if (_seconds > 0) next = next.AddSeconds(_seconds); // 启动循环。 while (true) { if (_break || _break_all) break; if (_now < next) { Sleep(); if (_break || _break_all) break; continue; } Invoke(); switch (_cycle) { case CronCycle.Minute: next = next.AddMinutes(1); break; case CronCycle.Hour: next = next.AddHours(1); break; case CronCycle.Day: next = next.AddDays(1); break; case CronCycle.Week: next = next.AddDays(7); break; default: _alive = false; return; } } } UsePool(pool => { if (pool.ContainsKey(_type)) pool.Remove(_type); _break = false; _alive = false; }); } void Invoke() { var caption = CaptionAttribute.Parse(_type); var sender = caption?.Title; if (sender.IsEmpty()) sender = _type.Name; if (_event) _logger.Text(sender, "Beginning"); var instance = null as object; try { instance = Activator.CreateInstance(_type); if (_event) _logger.Text(sender, "Ended"); } catch (Exception ex) { _logger.Exception(ex.InnerException, sender); } RuntimeUtility.Dispose(instance); } #endregion #region pool static Dictionary _pool = new Dictionary(); static void UsePool(Action> callback) { if (callback == null) throw new ArgumentNullException(nameof(callback)); lock (_pool) { callback.Invoke(_pool); } } static T UsePool(Func, T> callback) { if (callback == null) throw new ArgumentNullException(nameof(callback)); lock (_pool) { return callback.Invoke(_pool); } } #endregion #region invoker static bool _break_all = false; static DateTime _now; /// 获取状态,指示打断 Cron 循环。 public static bool Breaking { get => _break_all; } /// 正在运行的 Cron 数量。 public static int AliveCount { get => CountAlive(UsePool(pool => pool.Values)); } /// 获取正在运行的 Cron 数量。 /// static int CountAlive(IEnumerable crons) { var alive = 0; foreach (var cron in crons) { if (cron._alive) alive += 1; } return alive; } static CronAttribute[] Init(IEnumerable assemblies, Logger logger, bool logEvent) { var ab = new ArrayBuilder(); if (assemblies == null) assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var types = assembly.GetTypes(); foreach (var type in types) { if (!type.IsClass) continue; if (type.IsAbstract) continue; if (!RuntimeUtility.CanNew(type)) continue; var attributes = type.GetCustomAttributes(false); if (attributes == null) continue; foreach (var attribute in attributes) { var cron = attribute as CronAttribute; if (cron == null) continue; cron._type = type; cron._logger = logger; cron._event = logEvent; ab.Add(cron); } } } var array = ab.Export(); return array; } /// 启动指定的 Cron。 /// public static void Start(CronAttribute cron) { if (cron == null) throw new ArgumentNullException(nameof(cron)); cron.Run(); } /// 开始 Cron 调用(阻塞当前线程)。 /// 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索。 /// 日志记录程序,不指定此参数时将使用 Logger.Default。 /// 对 Cron 开始和结束记录日志。 /// /// 参数
/// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;
/// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default;
/// - logEvent:对 Cron 开始和结束记录日志。 ///
public static void Start(IEnumerable assemblies = null, Logger logger = null, bool logEvent = true) { if (logger == null) logger = Logger.Default; // 设置控制台事件。 Console.CancelKeyPress += (s, e) => { _break_all = true; e.Cancel = true; }; // 初始化。 _now = DateTime.Now; var crons = Init(assemblies, logger, logEvent); if (crons.Length > 0) { logger.Text(nameof(CronAttribute), "Crons started。"); // 启动 Cron。 foreach (var cron in crons) Start(cron); // 等待 Cron 退出。 while (true) { Thread.Sleep(100); _now = DateTime.Now; if (AliveCount < 1) break; } } logger.Text(nameof(CronAttribute), "Crons ended。"); } /// 打断指定的 Cron。 /// public static void Abort(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); UsePool(pool => { if (pool.TryGetValue(type, out var cron)) { try { cron._thread.Abort(); } catch { } } pool.Remove(type); }); } /// 打断指定的 Cron。 /// public static void Abort(object instance) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (instance is Type type) Abort(type); else Abort(instance.GetType()); } /// 打断所有 Cron。 public static void Abort() { UsePool(pool => { foreach (var item in pool) { try { item.Value._thread.Abort(); item.Value._alive = false; item.Value._break = false; } catch { } } }); } /// 打断 Cron 循环,不打断正在执行的 Job。 /// public static void Break(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); UsePool(pool => { if (pool.TryGetValue(type, out var cron)) { cron._break = true; } }); } /// 打断 Cron 循环,不打断正在执行的 Job。 /// public static void Break(object instance) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (instance is Type type) Break(type); else Break(instance.GetType()); } /// 打断 Cron 循环,不打断正在执行的 Job。 public static void Break() { _break_all = true; } /// 打断 Cron 循环并等待 Job 执行结束。等待指定的时间后打断正在执行的 Job。 /// 强制打断前的等待毫秒数,指定为负数时将无限等待。 public static void Break(int timeout) { _break_all = true; const int interval = 100; if (timeout < 0) { while (AliveCount > 0) Thread.Sleep(interval); return; } else { if (timeout > 0) { var remains = timeout; while (remains > 0 && AliveCount > 0) { Thread.Sleep(interval); remains -= interval; } } Abort(); } } #endregion } }