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
287 lines
9.8 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace Apewer
|
|
{
|
|
|
|
/// <summary>Cron 特性,默认间隔为 60 秒。</summary>
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
|
public sealed class CronAttribute : Attribute, IToJson
|
|
{
|
|
|
|
private int _mode = 0;
|
|
private int _seconds = 0;
|
|
private CronCycle _cycle = CronCycle.Once;
|
|
|
|
/// <summary>获取秒数。</summary>
|
|
public int Seconds { get { return _seconds; } }
|
|
|
|
/// <summary>创建 Cron 特性,可指定两次 Cron 执行的间隔秒数。</summary>
|
|
public CronAttribute(int seconds = 60)
|
|
{
|
|
_mode = 1;
|
|
_seconds = seconds;
|
|
if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
|
|
}
|
|
|
|
/// <summary>创建 Cron 特性,在每个周期中延迟指定秒数后执行。</summary>
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
public CronAttribute(CronCycle cycle, int seconds = 0)
|
|
{
|
|
_mode = 2;
|
|
_cycle = cycle;
|
|
_seconds = seconds;
|
|
if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds));
|
|
}
|
|
|
|
/// <summary>生成 Json 对象。</summary>
|
|
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;
|
|
}
|
|
|
|
#region Payload
|
|
|
|
// 传入。
|
|
Type _type = null;
|
|
Logger _logger = null;
|
|
bool _event = false;
|
|
|
|
// 实时。
|
|
Thread _thread = null;
|
|
bool _alive = false;
|
|
|
|
void Run()
|
|
{
|
|
_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;
|
|
if (_now < next)
|
|
{
|
|
Sleep();
|
|
if (_break) 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;
|
|
Sleep();
|
|
if (_break) 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;
|
|
if (_now < next)
|
|
{
|
|
Sleep();
|
|
if (_break) 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
_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 CronInvoker
|
|
|
|
static object _start = new object();
|
|
static CronAttribute[] _crons = null;
|
|
static bool _break = false;
|
|
static DateTime _now;
|
|
|
|
static CronAttribute[] Init(IEnumerable<Assembly> assemblies, Logger logger, bool logEvent)
|
|
{
|
|
var ab = new ArrayBuilder<CronAttribute>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>开始 Cron 调用(阻塞当前线程)。</summary>
|
|
/// <param name="assemblies">包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索。</param>
|
|
/// <param name="logger">日志记录程序,不指定此参数时将使用 Logger.Default。</param>
|
|
/// <param name="logEvent">对 Cron 开始和结束记录日志。</param>
|
|
/// <remarks>
|
|
/// 参数<br />
|
|
/// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
|
|
/// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default;<br />
|
|
/// - logEvent:对 Cron 开始和结束记录日志。
|
|
/// </remarks>
|
|
public static void Start(IEnumerable<Assembly> assemblies = null, Logger logger = null, bool logEvent = true)
|
|
{
|
|
if (logger == null) logger = Logger.Default;
|
|
lock (_start)
|
|
{
|
|
// 初始化。
|
|
_now = DateTime.Now;
|
|
_crons = Init(assemblies, logger, logEvent);
|
|
if (_crons.Length < 1)
|
|
{
|
|
logger.Error(nameof(CronAttribute), "没有找到带有 Cron 特性的类型。");
|
|
return;
|
|
}
|
|
|
|
// 启动线程。
|
|
Console.CancelKeyPress += (s, e) =>
|
|
{
|
|
_break = true;
|
|
e.Cancel = true;
|
|
};
|
|
logger.Text(nameof(CronAttribute), $"启动 {_crons.Length} 个 Cron 线程。");
|
|
foreach (var cron in _crons) cron.Run();
|
|
|
|
// 监视退出状态。
|
|
while (true)
|
|
{
|
|
Thread.Sleep(300);
|
|
_now = DateTime.Now;
|
|
var alive = 0;
|
|
for (var i = 0; i < _crons.Length; i++) if (_crons[i]._alive) alive += 1;
|
|
if (alive < 1) break;
|
|
}
|
|
logger.Text(nameof(CronAttribute), "所有 Cron 已结束。");
|
|
}
|
|
}
|
|
|
|
/// <summary>打断 Cron 循环,不打断正在执行的 Cron。</summary>
|
|
public static void Break()
|
|
{
|
|
_break = true;
|
|
}
|
|
|
|
/// <summary>获取状态,指示打断 Cron 循环。</summary>
|
|
public static bool Breaking { get => _break; }
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|