You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
9.8 KiB

4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Text;
  5. using System.Threading;
  6. namespace Apewer
  7. {
  8. /// <summary>Cron 特性,默认间隔为 60 秒。</summary>
  9. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
  10. public sealed class CronAttribute : Attribute, IToJson
  11. {
  12. private int _mode = 0;
  13. private int _seconds = 0;
  14. private CronCycle _cycle = CronCycle.Once;
  15. /// <summary>获取秒数。</summary>
  16. public int Seconds { get { return _seconds; } }
  17. /// <summary>创建 Cron 特性,可指定两次 Cron 执行的间隔秒数。</summary>
  18. public CronAttribute(int seconds = 60)
  19. {
  20. _mode = 1;
  21. _seconds = seconds;
  22. if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
  23. }
  24. /// <summary>创建 Cron 特性,在每个周期中延迟指定秒数后执行。</summary>
  25. /// <exception cref="ArgumentOutOfRangeException"></exception>
  26. public CronAttribute(CronCycle cycle, int seconds = 0)
  27. {
  28. _mode = 2;
  29. _cycle = cycle;
  30. _seconds = seconds;
  31. if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
  32. }
  33. /// <summary>生成 Json 对象。</summary>
  34. public Json ToJson()
  35. {
  36. var mode = _mode == 1 ? "interval" : "cycle";
  37. var type = _type == null ? null : _type.FullName;
  38. var json = Json.NewObject();
  39. json.SetProperty("type", type);
  40. json.SetProperty("mode", mode);
  41. if (_mode == 2) json.SetProperty("cycle", _cycle.ToString().ToLower());
  42. json.SetProperty("second", _seconds);
  43. return json;
  44. }
  45. #region Payload
  46. // 传入。
  47. Type _type = null;
  48. Logger _logger = null;
  49. bool _event = false;
  50. // 实时。
  51. Thread _thread = null;
  52. bool _alive = false;
  53. void Run()
  54. {
  55. _alive = true;
  56. _thread = new Thread(Payload);
  57. _thread.IsBackground = false;
  58. _thread.Start();
  59. }
  60. // 当前进程睡眠。
  61. static void Sleep() => Thread.Sleep(500);
  62. // 线程负载。
  63. void Payload()
  64. {
  65. // 基于间隔时间调用。
  66. if (_mode == 1)
  67. {
  68. // 初始值为最小值,保证第一次启动时必定运行。
  69. var next = DateTime.MinValue;
  70. while (true)
  71. {
  72. if (_break) break;
  73. if (_now < next)
  74. {
  75. Sleep();
  76. if (_break) break;
  77. continue;
  78. }
  79. Invoke();
  80. next = DateTime.Now.AddSeconds(_seconds);
  81. }
  82. }
  83. // 仅执行一次,延迟启动。
  84. if (_mode == 2 && _cycle == CronCycle.Once)
  85. {
  86. var next = DateTime.Now.AddSeconds(_seconds);
  87. while (_now < next)
  88. {
  89. if (_break) break;
  90. Sleep();
  91. if (_break) break;
  92. }
  93. Invoke();
  94. }
  95. // 基于周期调用。
  96. if (_mode == 2 && _cycle != CronCycle.Once)
  97. {
  98. // 计算当前期间的运行时间。
  99. DateTime next = DateTime.Now;
  100. switch (_cycle)
  101. {
  102. case CronCycle.Minute:
  103. next = new DateTime(next.Year, next.Month, next.Day, next.Hour, next.Minute, 0, 0);
  104. break;
  105. case CronCycle.Hour:
  106. next = new DateTime(next.Year, next.Month, next.Day, next.Hour, 0, 0, 0);
  107. break;
  108. case CronCycle.Day:
  109. next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0);
  110. break;
  111. case CronCycle.Week:
  112. next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0).AddDays(-(int)next.DayOfWeek);
  113. break;
  114. }
  115. if (_seconds > 0) next = next.AddSeconds(_seconds);
  116. // 启动循环。
  117. while (true)
  118. {
  119. if (_break) break;
  120. if (_now < next)
  121. {
  122. Sleep();
  123. if (_break) break;
  124. continue;
  125. }
  126. Invoke();
  127. switch (_cycle)
  128. {
  129. case CronCycle.Minute:
  130. next = next.AddMinutes(1);
  131. break;
  132. case CronCycle.Hour:
  133. next = next.AddHours(1);
  134. break;
  135. case CronCycle.Day:
  136. next = next.AddDays(1);
  137. break;
  138. case CronCycle.Week:
  139. next = next.AddDays(7);
  140. break;
  141. default:
  142. _alive = false;
  143. return;
  144. }
  145. }
  146. }
  147. _alive = false;
  148. }
  149. void Invoke()
  150. {
  151. var caption = CaptionAttribute.Parse(_type);
  152. var sender = caption?.Title;
  153. if (sender.IsEmpty()) sender = _type.Name;
  154. if (_event) _logger.Text(sender, "Beginning");
  155. var instance = null as object;
  156. try
  157. {
  158. instance = Activator.CreateInstance(_type);
  159. if (_event) _logger.Text(sender, "Ended");
  160. }
  161. catch (Exception ex)
  162. {
  163. _logger.Exception(ex.InnerException, sender);
  164. }
  165. RuntimeUtility.Dispose(instance);
  166. }
  167. #endregion
  168. #region CronInvoker
  169. static object _start = new object();
  170. static CronAttribute[] _crons = null;
  171. static bool _break = false;
  172. static DateTime _now;
  173. static CronAttribute[] Init(IEnumerable<Assembly> assemblies, Logger logger, bool logEvent)
  174. {
  175. var ab = new ArrayBuilder<CronAttribute>();
  176. if (assemblies == null) assemblies = AppDomain.CurrentDomain.GetAssemblies();
  177. foreach (var assembly in assemblies)
  178. {
  179. var types = assembly.GetTypes();
  180. foreach (var type in types)
  181. {
  182. if (!type.IsClass) continue;
  183. if (type.IsAbstract) continue;
  184. if (!RuntimeUtility.CanNew(type)) continue;
  185. var attributes = type.GetCustomAttributes(false);
  186. if (attributes == null) continue;
  187. foreach (var attribute in attributes)
  188. {
  189. var cron = attribute as CronAttribute;
  190. if (cron == null) continue;
  191. cron._type = type;
  192. cron._logger = logger;
  193. cron._event = logEvent;
  194. ab.Add(cron);
  195. }
  196. }
  197. }
  198. var array = ab.Export();
  199. return array;
  200. }
  201. /// <summary>开始 Cron 调用(阻塞当前线程)。</summary>
  202. /// <param name="assemblies">包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索。</param>
  203. /// <param name="logger">日志记录程序,不指定此参数时将使用 Logger.Default。</param>
  204. /// <param name="logEvent">对 Cron 开始和结束记录日志。</param>
  205. /// <remarks>
  206. /// 参数<br />
  207. /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
  208. /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default;<br />
  209. /// - logEvent:对 Cron 开始和结束记录日志。
  210. /// </remarks>
  211. public static void Start(IEnumerable<Assembly> assemblies = null, Logger logger = null, bool logEvent = true)
  212. {
  213. if (logger == null) logger = Logger.Default;
  214. lock (_start)
  215. {
  216. // 初始化。
  217. _now = DateTime.Now;
  218. _crons = Init(assemblies, logger, logEvent);
  219. if (_crons.Length < 1)
  220. {
  221. logger.Error(nameof(CronAttribute), "没有找到带有 Cron 特性的类型。");
  222. return;
  223. }
  224. // 启动线程。
  225. Console.CancelKeyPress += (s, e) =>
  226. {
  227. _break = true;
  228. e.Cancel = true;
  229. };
  230. logger.Text(nameof(CronAttribute), $"启动 {_crons.Length} 个 Cron 线程。");
  231. foreach (var cron in _crons) cron.Run();
  232. // 监视退出状态。
  233. while (true)
  234. {
  235. Thread.Sleep(300);
  236. _now = DateTime.Now;
  237. var alive = 0;
  238. for (var i = 0; i < _crons.Length; i++) if (_crons[i]._alive) alive += 1;
  239. if (alive < 1) break;
  240. }
  241. logger.Text(nameof(CronAttribute), "所有 Cron 已结束。");
  242. }
  243. }
  244. /// <summary>打断 Cron 循环,不打断正在执行的 Cron。</summary>
  245. public static void Break()
  246. {
  247. _break = true;
  248. }
  249. /// <summary>获取状态,指示打断 Cron 循环。</summary>
  250. public static bool Breaking { get => _break; }
  251. #endregion
  252. }
  253. }