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.

331 lines
11 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
4 years ago
3 years ago
4 years ago
4 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 int _crons_alive = 0;
  174. static CronAttribute[] Init(IEnumerable<Assembly> assemblies, Logger logger, bool logEvent)
  175. {
  176. var ab = new ArrayBuilder<CronAttribute>();
  177. if (assemblies == null) assemblies = AppDomain.CurrentDomain.GetAssemblies();
  178. foreach (var assembly in assemblies)
  179. {
  180. var types = assembly.GetTypes();
  181. foreach (var type in types)
  182. {
  183. if (!type.IsClass) continue;
  184. if (type.IsAbstract) continue;
  185. if (!RuntimeUtility.CanNew(type)) continue;
  186. var attributes = type.GetCustomAttributes(false);
  187. if (attributes == null) continue;
  188. foreach (var attribute in attributes)
  189. {
  190. var cron = attribute as CronAttribute;
  191. if (cron == null) continue;
  192. cron._type = type;
  193. cron._logger = logger;
  194. cron._event = logEvent;
  195. ab.Add(cron);
  196. }
  197. }
  198. }
  199. var array = ab.Export();
  200. return array;
  201. }
  202. /// <summary>开始 Cron 调用(阻塞当前线程)。</summary>
  203. /// <param name="assemblies">包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索。</param>
  204. /// <param name="logger">日志记录程序,不指定此参数时将使用 Logger.Default。</param>
  205. /// <param name="logEvent">对 Cron 开始和结束记录日志。</param>
  206. /// <remarks>
  207. /// 参数<br />
  208. /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
  209. /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default;<br />
  210. /// - logEvent:对 Cron 开始和结束记录日志。
  211. /// </remarks>
  212. public static void Start(IEnumerable<Assembly> assemblies = null, Logger logger = null, bool logEvent = true)
  213. {
  214. if (logger == null) logger = Logger.Default;
  215. lock (_start)
  216. {
  217. // 初始化。
  218. _now = DateTime.Now;
  219. _crons = Init(assemblies, logger, logEvent);
  220. if (_crons.Length < 1)
  221. {
  222. logger.Error(nameof(CronAttribute), "没有找到带有 Cron 特性的类型。");
  223. return;
  224. }
  225. // 启动线程。
  226. _crons_alive = _crons.Length;
  227. Console.CancelKeyPress += (s, e) =>
  228. {
  229. _break = true;
  230. e.Cancel = true;
  231. };
  232. logger.Text(nameof(CronAttribute), $"启动 {_crons.Length} 个 Cron 线程。");
  233. foreach (var cron in _crons) cron.Run();
  234. // 监视退出状态。
  235. while (true)
  236. {
  237. Thread.Sleep(100);
  238. _now = DateTime.Now;
  239. var alive = 0;
  240. for (var i = 0; i < _crons.Length; i++) if (_crons[i]._alive) alive += 1;
  241. if (alive < 1) break;
  242. _crons_alive = alive;
  243. }
  244. logger.Text(nameof(CronAttribute), "所有 Cron 已结束。");
  245. }
  246. }
  247. /// <summary>打断正在执行的 Cron。</summary>
  248. public static void Abort()
  249. {
  250. var crons = _crons;
  251. if (crons != null)
  252. {
  253. foreach (var cron in crons)
  254. {
  255. if (cron == null) continue;
  256. try { cron._thread.Abort(); } catch { }
  257. }
  258. }
  259. }
  260. /// <summary>打断 Cron 循环,不打断正在执行的 Cron。</summary>
  261. public static void Break()
  262. {
  263. _break = true;
  264. }
  265. /// <summary>打断 Cron 循环并等待 Cron 执行结束。等待指定的时间后打断正在执行的 Cron。</summary>
  266. /// <param name="timeout">强制打断前的等待毫秒数,指定为负数时将无限等待。</param>
  267. public static void Break(int timeout)
  268. {
  269. _break = true;
  270. const int interval = 100;
  271. if (timeout < 0)
  272. {
  273. while (_crons_alive > 0) Thread.Sleep(interval);
  274. return;
  275. }
  276. else
  277. {
  278. if (timeout > 0)
  279. {
  280. var remains = timeout;
  281. while (remains > 0 && _crons_alive > 0)
  282. {
  283. Thread.Sleep(interval);
  284. remains -= interval;
  285. }
  286. }
  287. Abort();
  288. }
  289. }
  290. /// <summary>获取状态,指示打断 Cron 循环。</summary>
  291. public static bool Breaking { get => _break; }
  292. #endregion
  293. }
  294. }