如何在 .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 中每小时(或每小时的特定时间间隔)引发一个事件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Prometheus 中每小时正确抓取和查询指标

每小时或每天都有一个卡夫卡主题是典型的吗?

在特定的时间段内每天以每小时间隔运行本地通知

SQL:如何获取每个数据库或每小时或...的执行查询数?

如何在 .Net 5.0 C# 或 F# 中编写以特定时间间隔定期运行的代码

使用 pandas 的 datetimeindex 在特定日期按分钟和小时提取时间间隔