如何在 .NET 中每小时(或每小时的特定时间间隔)引发一个事件?
Posted
技术标签:
【中文标题】如何在 .NET 中每小时(或每小时的特定时间间隔)引发一个事件?【英文标题】:How can I raise an event every hour (or specific time interval each hour) in .NET? 【发布时间】:2010-09-23 09:29:38 【问题描述】:我正在开发一个小型网络爬虫,它将在系统托盘中运行,每小时整点爬一次网站。
让 .NET 每小时或其他间隔引发事件以执行某些任务的最佳方法是什么。例如,我想根据时间每 20 分钟运行一次活动。该事件将在以下时间提出:
00:20
00:40
01:00
01:20
01:40
等等。我能想到的最好的方法是在线程上创建一个循环,不断检查时间是否可以被给定的时间间隔整除,如果达到时间则引发回调事件。我觉得必须有更好的方法。
我会使用Timer
,但我更喜欢遵循按小时运行的“时间表”或类似的东西。
如果不在 Windows 任务计划程序中设置我的应用程序,这可能吗?
更新:
我正在添加我的算法来计算计时器的时间间隔。此方法采用“minute
”参数,即计时器应触发滴答的时间。例如,如果“minute
”参数为20,那么定时器会按照上面时间表中的间隔计时。
int CalculateTimerInterval(int minute)
if (minute <= 0)
minute = 60;
DateTime now = DateTime.Now;
DateTime future = now.AddMinutes((minute - (now.Minute % minute))).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
TimeSpan interval = future - now;
return (int)interval.TotalMilliseconds;
这段代码使用如下:
static System.Windows.Forms.Timer t;
const int CHECK_INTERVAL = 20;
static void Main()
t = new System.Windows.Forms.Timer();
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
t.Tick += new EventHandler(t_Tick);
t.Start();
static void t_Tick(object sender, EventArgs e)
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
【问题讨论】:
【参考方案1】:System.Timers.Timer。如果您想在一天中的特定时间跑步,则需要计算距离下一次跑步还有多长时间,并将其设置为您的时间间隔。
这只是基本的想法。根据您需要的精确程度,您可以做得更多。
int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60 * 1000;
【讨论】:
同意 - 计时器是要走的路。 “检查的循环”只是说“轮询”和轮询杀死的另一种方式。使用计时器是最好的选择。 进行了编辑,但直到它被批准:timer.Interval = adjust * 60 * 1000。间隔以毫秒为单位。更多详情请见Timer.Interval 仅供参考,如果您希望事件每十分钟触发一次,则必须将间隔设置为第一次触发 Elapsed 事件后的 10 分钟。如果不是,那么无论初始间隔设置为 (10 - (分钟 % 10)),该事件都将继续触发。【参考方案2】:您可以从 Quartz.net http://quartznet.sourceforge.net/ 获得帮助
【讨论】:
Quartz.net 看起来是一个很棒的库,但对于我正在制作的小应用程序,我认为它有点矫枉过正(我的应用程序不到 500 行代码)。 即使是小型应用程序,Quartz 也非常灵活,并且在代码行数方面增加的开销非常小。大概六行代码用于定时作业的初始化和调度。 任何简单的小样本??我下载了它,我有很多代码,我需要一些简单易用的东西 @BrianVallelunga 我在 wondows 表单应用程序中使用 Quartz。当我调试时它工作正常,但当我创建安装项目并安装它时它不工作。有什么想法吗?【参考方案3】:这是一个使用线程计时和异步调用的轻量级系统示例。
我知道有一些缺点,但我喜欢在启动长时间运行的进程(如计划的后端服务)时使用它而不是计时器。由于它在计时器线程中内联运行,因此您不必担心它会在原始调用完成之前再次启动。这可以扩展很多,使其使用日期时间数组作为触发时间,或者为其添加更多功能。我相信你们中的一些人知道一些更好的方法。
public Form1()
InitializeComponent();
//some fake data, obviously you would have your own.
DateTime someStart = DateTime.Now.AddMinutes(1);
TimeSpan someInterval = TimeSpan.FromMinutes(2);
//sample call
StartTimer(someStart,someInterval,doSomething);
//just a fake function to call
private bool doSomething()
DialogResult keepGoing = MessageBox.Show("Hey, I did something! Keep Going?","Something!",MessageBoxButtons.YesNo);
return (keepGoing == DialogResult.Yes);
//The following is the actual guts.. and can be transplanted to an actual class.
private delegate void voidFunc<P1,P2,P3>(P1 p1,P2 p2,P3 p3);
public void StartTimer(DateTime startTime, TimeSpan interval, Func<bool> action)
voidFunc<DateTime,TimeSpan,Func<bool>> Timer = TimedThread;
Timer.BeginInvoke(startTime,interval,action,null,null);
private void TimedThread(DateTime startTime, TimeSpan interval, Func<bool> action)
bool keepRunning = true;
DateTime NextExecute = startTime;
while(keepRunning)
if (DateTime.Now > NextExecute)
keepRunning = action.Invoke();
NextExecute = NextExecute.Add(interval);
//could parameterize resolution.
Thread.Sleep(1000);
【讨论】:
【参考方案4】:另一种策略是记录进程运行的最后时间,并确定自该时间以来您所需的时间间隔是否已过。在此策略中,如果经过的时间等于或大于所需的时间间隔,您将编写要触发的事件。通过这种方式,您可以处理如果计算机因某种原因停机时可能会错过长时间间隔(例如每天一次)的情况。
例如:
lastRunDateTime = 2009 年 5 月 2 日晚上 8 点 我想每 24 小时运行一次进程 在计时器事件中,检查自上次运行进程以来是否经过了 24 小时或更长时间。 如果是,请运行该进程,通过向其添加所需的时间间隔来更新 lastRunDateTime(在本例中为 24 小时,但无论您需要什么时间)显然,为了在系统关闭后恢复,您需要将 lastRunDateTime 存储在某个文件或数据库中,以便程序可以在恢复时从中断的地方继续。
【讨论】:
【参考方案5】:System.Windows.Forms.Timer(或 System.Timers.Timer)
但是既然你现在说你不想使用定时器,你可以在另一个线程上运行一个轻量级的等待进程(检查时间,休眠几秒钟,再次检查时间......)或者制作一个引发在某些预定时间或间隔发生事件(使用轻量级等待过程)
【讨论】:
【参考方案6】:以下应该可以解决问题。
static void Main(string[] Args)
try
MainAsync().GetAwaiter().GetResult();
catch (Exception ex)
Console.WriteLine(ex.Message);
static async Task MainAsync()
CancellationTokenSource tokenSource = new CancellationTokenSource();
// Start the timed event here
StartAsync(tokenSource.Token);
Console.ReadKey();
tokenSource.Cancel();
tokenSource.Dispose();
public Task StartAsync(CancellationToken cancellationToken)
var nextRunTime = new DateTime();
switch (DateTime.Now.AddSeconds(1) < DateTime.Today.AddHours(12)) // add a second to current time to account for time needed to setup the task.
case true:
nextRunTime = DateTime.Today.AddHours(12); // Run at midday today.
break;
case false:
nextRunTime = DateTime.Today.AddDays(1).AddHours(12); // Run at midday tomorrow.
break;
var firstInterval = nextRunTime.Subtract(DateTime.Now);
Action action = () =>
// Run the task at the first interval, then run the task again at midday every day.
_timer = new Timer(
EventMethod,
null,
firstInterval,
DateTime.Today.AddDays(1).AddHours(12).Subtract(DateTime.Now)
);
;
// no need to await this call here because this task is scheduled to run later.
Task.Run(action);
return Task.CompletedTask;
private async void EventMethod(object state)
// do work
【讨论】:
【参考方案7】:我的目标是每晚 03:00 左右运行一次导入。
这是我的方法,使用 System.Timers.Timer:
private Timer _timer;
private Int32 _hours = 0;
private Int32 _runAt = 3;
protected override void OnStart(string[] args)
_hours = (24 - (DateTime.Now.Hour + 1)) + _runAt;
_timer = new Timer();
_timer.Interval = _hours * 60 * 60 * 1000;
_timer.Elapsed += new ElapsedEventHandler(Tick);
_timer.Start();
void Tick(object sender, ElapsedEventArgs e)
if (_hours != 24)
_hours = 24;
_timer.Interval = _hours * 60 * 60 * 1000;
RunImport();
【讨论】:
如果您正在运行每日导入,这将是一个坏主意。您最好使用 Windows 计划任务来保证在凌晨 3:00 运行导入。根据您的要求,您的方法有几个潜在的危险错误。 如果计算机上的时间发生变化,它不会在凌晨 3 点开始,或者如果在夏令时运行,它会关闭一个小时。如果您在 IIS 应用程序池的上下文中运行,它也根本不可能运行。我的原始示例只有一个 20 分钟的计时器,因此如果出现问题,误差范围是几分钟,并且它位于桌面应用程序中,因此它可能会继续运行,但即使是我的原始示例也更适合Windows 计划任务。以上是关于如何在 .NET 中每小时(或每小时的特定时间间隔)引发一个事件?的主要内容,如果未能解决你的问题,请参考以下文章