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.

463 lines
15 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 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
4 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
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 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
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
4 years ago
4 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
4 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.Design;
  4. using System.Reflection;
  5. using System.Text;
  6. using System.Threading;
  7. namespace Apewer
  8. {
  9. /// <summary>Cron 特性,默认间隔为 60 秒。</summary>
  10. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
  11. public sealed class CronAttribute : Attribute, IToJson
  12. {
  13. #region attribute
  14. private int _mode = 0;
  15. private int _seconds = 0;
  16. private CronCycle _cycle = CronCycle.Once;
  17. /// <summary>获取秒数。</summary>
  18. public int Seconds { get { return _seconds; } }
  19. /// <summary>创建 Cron 特性,可指定两次 Cron 执行的间隔秒数。</summary>
  20. public CronAttribute(int seconds = 60)
  21. {
  22. _mode = 1;
  23. _seconds = seconds;
  24. if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
  25. }
  26. /// <summary>创建 Cron 特性,在每个周期中延迟指定秒数后执行。</summary>
  27. /// <exception cref="ArgumentOutOfRangeException"></exception>
  28. public CronAttribute(CronCycle cycle, int seconds = 0)
  29. {
  30. _mode = 2;
  31. _cycle = cycle;
  32. _seconds = seconds;
  33. if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
  34. }
  35. /// <summary>生成 Json 对象。</summary>
  36. public Json ToJson()
  37. {
  38. var mode = _mode == 1 ? "interval" : "cycle";
  39. var type = _type == null ? null : _type.FullName;
  40. var json = Json.NewObject();
  41. json.SetProperty("type", type);
  42. json.SetProperty("mode", mode);
  43. if (_mode == 2) json.SetProperty("cycle", _cycle.ToString().ToLower());
  44. json.SetProperty("second", _seconds);
  45. return json;
  46. }
  47. /// <summary></summary>
  48. public override string ToString()
  49. {
  50. if (_type != null)
  51. {
  52. if (_mode == 1) return $"Type = {_type.FullName}, Seconds = {_seconds}";
  53. if (_mode == 2) return $"Type = {_type.FullName}, Cycle = {_cycle}, Seconds = {_seconds}";
  54. }
  55. return base.ToString();
  56. }
  57. #endregion
  58. #region payload
  59. /// <summary>执行 Cron 的类。</summary>
  60. public Type Type { get => _type; }
  61. // 传入。
  62. Type _type = null;
  63. Logger _logger = null;
  64. bool _event = false;
  65. // 实时。
  66. Thread _thread = null;
  67. bool _alive = false;
  68. bool _break = false;
  69. /// <summary>运行当前 Cron。</summary>
  70. void Run()
  71. {
  72. if (_alive) return;
  73. if (_type == null) return;
  74. _break = false;
  75. UsePool(pool =>
  76. {
  77. if (pool.ContainsKey(_type)) return;
  78. pool.Add(_type, this);
  79. _alive = true;
  80. _thread = new Thread(Payload);
  81. _thread.IsBackground = false;
  82. _thread.Start();
  83. });
  84. }
  85. // 当前进程睡眠。
  86. static void Sleep() => Thread.Sleep(500);
  87. // 线程负载。
  88. void Payload()
  89. {
  90. // 基于间隔时间调用。
  91. if (_mode == 1)
  92. {
  93. // 初始值为最小值,保证第一次启动时必定运行。
  94. var next = DateTime.MinValue;
  95. while (true)
  96. {
  97. if (_break || _break_all) break;
  98. if (_now < next)
  99. {
  100. Sleep();
  101. if (_break || _break_all) break;
  102. continue;
  103. }
  104. Invoke();
  105. next = DateTime.Now.AddSeconds(_seconds);
  106. }
  107. }
  108. // 仅执行一次,延迟启动。
  109. if (_mode == 2 && _cycle == CronCycle.Once)
  110. {
  111. var next = DateTime.Now.AddSeconds(_seconds);
  112. while (_now < next)
  113. {
  114. if (_break || _break_all) break;
  115. Sleep();
  116. if (_break || _break_all) break;
  117. }
  118. Invoke();
  119. }
  120. // 基于周期调用。
  121. if (_mode == 2 && _cycle != CronCycle.Once)
  122. {
  123. // 计算当前期间的运行时间。
  124. DateTime next = DateTime.Now;
  125. switch (_cycle)
  126. {
  127. case CronCycle.Minute:
  128. next = new DateTime(next.Year, next.Month, next.Day, next.Hour, next.Minute, 0, 0);
  129. break;
  130. case CronCycle.Hour:
  131. next = new DateTime(next.Year, next.Month, next.Day, next.Hour, 0, 0, 0);
  132. break;
  133. case CronCycle.Day:
  134. next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0);
  135. break;
  136. case CronCycle.Week:
  137. next = new DateTime(next.Year, next.Month, next.Day, 0, 0, 0, 0).AddDays(-(int)next.DayOfWeek);
  138. break;
  139. }
  140. if (_seconds > 0) next = next.AddSeconds(_seconds);
  141. // 启动循环。
  142. while (true)
  143. {
  144. if (_break || _break_all) break;
  145. if (_now < next)
  146. {
  147. Sleep();
  148. if (_break || _break_all) break;
  149. continue;
  150. }
  151. Invoke();
  152. switch (_cycle)
  153. {
  154. case CronCycle.Minute:
  155. next = next.AddMinutes(1);
  156. break;
  157. case CronCycle.Hour:
  158. next = next.AddHours(1);
  159. break;
  160. case CronCycle.Day:
  161. next = next.AddDays(1);
  162. break;
  163. case CronCycle.Week:
  164. next = next.AddDays(7);
  165. break;
  166. default:
  167. _alive = false;
  168. return;
  169. }
  170. }
  171. }
  172. UsePool(pool =>
  173. {
  174. if (pool.ContainsKey(_type)) pool.Remove(_type);
  175. _break = false;
  176. _alive = false;
  177. });
  178. }
  179. void Invoke()
  180. {
  181. var caption = CaptionAttribute.Parse(_type);
  182. var sender = caption?.Title;
  183. if (sender.IsEmpty()) sender = _type.Name;
  184. if (_event) _logger.Text(sender, "Beginning");
  185. var instance = null as object;
  186. try
  187. {
  188. instance = Activator.CreateInstance(_type);
  189. if (_event) _logger.Text(sender, "Ended");
  190. }
  191. catch (Exception ex)
  192. {
  193. _logger.Exception(ex.InnerException, sender);
  194. }
  195. RuntimeUtility.Dispose(instance);
  196. }
  197. #endregion
  198. #region pool
  199. static Dictionary<Type, CronAttribute> _pool = new Dictionary<Type, CronAttribute>();
  200. static void UsePool(Action<Dictionary<Type, CronAttribute>> callback)
  201. {
  202. if (callback == null) throw new ArgumentNullException(nameof(callback));
  203. lock (_pool)
  204. {
  205. callback.Invoke(_pool);
  206. }
  207. }
  208. static T UsePool<T>(Func<Dictionary<Type, CronAttribute>, T> callback)
  209. {
  210. if (callback == null) throw new ArgumentNullException(nameof(callback));
  211. lock (_pool)
  212. {
  213. return callback.Invoke(_pool);
  214. }
  215. }
  216. #endregion
  217. #region invoker
  218. static bool _break_all = false;
  219. static DateTime _now;
  220. /// <summary>获取状态,指示打断 Cron 循环。</summary>
  221. public static bool Breaking { get => _break_all; }
  222. /// <summary>正在运行的 Cron 数量。</summary>
  223. public static int AliveCount { get => CountAlive(UsePool(pool => pool.Values)); }
  224. /// <summary>获取正在运行的 Cron 数量。</summary>
  225. /// <returns></returns>
  226. static int CountAlive(IEnumerable<CronAttribute> crons)
  227. {
  228. var alive = 0;
  229. foreach (var cron in crons)
  230. {
  231. if (cron._alive) alive += 1;
  232. }
  233. return alive;
  234. }
  235. static CronAttribute[] Init(IEnumerable<Assembly> assemblies, Logger logger, bool logEvent)
  236. {
  237. var ab = new ArrayBuilder<CronAttribute>();
  238. if (assemblies == null) assemblies = AppDomain.CurrentDomain.GetAssemblies();
  239. foreach (var assembly in assemblies)
  240. {
  241. var types = assembly.GetTypes();
  242. foreach (var type in types)
  243. {
  244. if (!type.IsClass) continue;
  245. if (type.IsAbstract) continue;
  246. if (!RuntimeUtility.CanNew(type)) continue;
  247. var attributes = type.GetCustomAttributes(false);
  248. if (attributes == null) continue;
  249. foreach (var attribute in attributes)
  250. {
  251. var cron = attribute as CronAttribute;
  252. if (cron == null) continue;
  253. cron._type = type;
  254. cron._logger = logger;
  255. cron._event = logEvent;
  256. ab.Add(cron);
  257. }
  258. }
  259. }
  260. var array = ab.Export();
  261. return array;
  262. }
  263. /// <summary>启动指定的 Cron。</summary>
  264. /// <exception cref="ArgumentNullException"></exception>
  265. public static void Start(CronAttribute cron)
  266. {
  267. if (cron == null) throw new ArgumentNullException(nameof(cron));
  268. cron.Run();
  269. }
  270. /// <summary>开始 Cron 调用(阻塞当前线程)。</summary>
  271. /// <param name="assemblies">包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索。</param>
  272. /// <param name="logger">日志记录程序,不指定此参数时将使用 Logger.Default。</param>
  273. /// <param name="logEvent">对 Cron 开始和结束记录日志。</param>
  274. /// <remarks>
  275. /// 参数<br />
  276. /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
  277. /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default;<br />
  278. /// - logEvent:对 Cron 开始和结束记录日志。
  279. /// </remarks>
  280. public static void Start(IEnumerable<Assembly> assemblies = null, Logger logger = null, bool logEvent = true)
  281. {
  282. if (logger == null) logger = Logger.Default;
  283. // 设置控制台事件。
  284. Console.CancelKeyPress += (s, e) =>
  285. {
  286. _break_all = true;
  287. e.Cancel = true;
  288. };
  289. // 初始化。
  290. _now = DateTime.Now;
  291. var crons = Init(assemblies, logger, logEvent);
  292. if (crons.Length > 0)
  293. {
  294. logger.Text(nameof(CronAttribute), "Crons started。");
  295. // 启动 Cron。
  296. foreach (var cron in crons) Start(cron);
  297. // 等待 Cron 退出。
  298. while (true)
  299. {
  300. Thread.Sleep(100);
  301. _now = DateTime.Now;
  302. if (AliveCount < 1) break;
  303. }
  304. }
  305. logger.Text(nameof(CronAttribute), "Crons ended。");
  306. }
  307. /// <summary>打断指定的 Cron。</summary>
  308. /// <exception cref="ArgumentNullException" />
  309. public static void Abort(Type type)
  310. {
  311. if (type == null) throw new ArgumentNullException(nameof(type));
  312. UsePool(pool =>
  313. {
  314. if (pool.TryGetValue(type, out var cron))
  315. {
  316. try { cron._thread.Abort(); } catch { }
  317. }
  318. pool.Remove(type);
  319. });
  320. }
  321. /// <summary>打断指定的 Cron。</summary>
  322. /// <exception cref="ArgumentNullException" />
  323. public static void Abort(object instance)
  324. {
  325. if (instance == null) throw new ArgumentNullException(nameof(instance));
  326. if (instance is Type type) Abort(type);
  327. else Abort(instance.GetType());
  328. }
  329. /// <summary>打断所有 Cron。</summary>
  330. public static void Abort()
  331. {
  332. UsePool(pool =>
  333. {
  334. foreach (var item in pool)
  335. {
  336. try
  337. {
  338. item.Value._thread.Abort();
  339. item.Value._alive = false;
  340. item.Value._break = false;
  341. }
  342. catch { }
  343. }
  344. });
  345. }
  346. /// <summary>打断 Cron 循环,不打断正在执行的 Job。</summary>
  347. /// <exception cref="ArgumentNullException" />
  348. public static void Break(Type type)
  349. {
  350. if (type == null) throw new ArgumentNullException(nameof(type));
  351. UsePool(pool =>
  352. {
  353. if (pool.TryGetValue(type, out var cron))
  354. {
  355. cron._break = true;
  356. }
  357. });
  358. }
  359. /// <summary>打断 Cron 循环,不打断正在执行的 Job。</summary>
  360. /// <exception cref="ArgumentNullException" />
  361. public static void Break(object instance)
  362. {
  363. if (instance == null) throw new ArgumentNullException(nameof(instance));
  364. if (instance is Type type) Break(type);
  365. else Break(instance.GetType());
  366. }
  367. /// <summary>打断 Cron 循环,不打断正在执行的 Job。</summary>
  368. public static void Break()
  369. {
  370. _break_all = true;
  371. }
  372. /// <summary>打断 Cron 循环并等待 Job 执行结束。等待指定的时间后打断正在执行的 Job。</summary>
  373. /// <param name="timeout">强制打断前的等待毫秒数,指定为负数时将无限等待。</param>
  374. public static void Break(int timeout)
  375. {
  376. _break_all = true;
  377. const int interval = 100;
  378. if (timeout < 0)
  379. {
  380. while (AliveCount > 0) Thread.Sleep(interval);
  381. return;
  382. }
  383. else
  384. {
  385. if (timeout > 0)
  386. {
  387. var remains = timeout;
  388. while (remains > 0 && AliveCount > 0)
  389. {
  390. Thread.Sleep(interval);
  391. remains -= interval;
  392. }
  393. }
  394. Abort();
  395. }
  396. }
  397. #endregion
  398. }
  399. }