【问题标题】:How can I raise an event every hour (or specific time interval each hour) in .NET?如何在 .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);
}

【问题讨论】:

    标签: c# .net scheduled-tasks


    【解决方案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 计划任务。
            猜你喜欢
            • 2012-10-03
            • 2015-08-14
            • 2021-08-22
            • 1970-01-01
            • 2017-11-01
            • 2012-08-31
            • 1970-01-01
            • 1970-01-01
            • 2018-09-23
            相关资源
            最近更新 更多