31

我正在开发一个小型网络爬虫,它将在系统托盘中运行并每小时整点爬取一个网站。

让 .NET 每小时或其他间隔引发事件以执行某些任务的最佳方法是什么。例如,我想根据时间每 20 分钟运行一次活动。该活动将在以下地点提出:

00:20
00:40
01:00
01:20
01:40

等等。我能想到的最好方法是在线程上创建一个循环,不断检查时间是否可以被给定的时间间隔整除,如果达到时间则引发回调事件。我觉得必须有更好的方法。

我会使用 aTimer但我更喜欢遵循按小时运行的“时间表”或类似的东西。

如果没有在 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);
}
4

7 回答 7

31

系统.计时器.计时器。如果您想在一天中的特定时间跑步,您将需要计算距离下一次跑步还有多长时间,并将其设置为您的时间间隔。

这只是基本的想法。根据您需要的精确程度,您可以做得更多。

int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60 * 1000;  
于 2008-11-21T04:01:53.560 回答
11

您可以从 Quartz.net http://quartznet.sourceforge.net/获得帮助

于 2008-11-21T04:03:34.587 回答
4

这是一个使用线程计时和异步调用的轻量级系统示例。

我知道有一些缺点,但我喜欢在启动长时间运行的进程(如计划的后端服务)时使用它而不是计时器。由于它在计时器线程中内联运行,因此您不必担心它会在原始调用完成之前再次启动。这可以扩展很多,使其使用日期时间数组作为触发时间,或者为其添加更多功能。我相信你们中的一些人知道一些更好的方法。

    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);
        }            
    }
于 2008-11-21T06:35:56.540 回答
2

另一种策略是记录进程运行的最后时间,并确定自该时间以来您所需的时间间隔是否已过。在此策略中,如果经过的时间等于或大于所需的时间间隔,您将编写要触发的事件。通过这种方式,您可以处理如果计算机因某种原因停机时可能会错过长时间间隔(例如,每天一次)的情况。

例如:

  • lastRunDateTime = 2009 年 5 月 2 日晚上 8 点
  • 我想每 24 小时运行一次我的进程
  • 在计时器事件中,检查自上次运行进程以来是否经过了 24 小时或更长时间。
  • 如果是,请运行该过程,通过向其添加所需的时间间隔来更新 lastRunDateTime(在这种情况下为 24 小时,但无论您需要它是什么)

显然,为了在系统关闭后恢复,您需要将 lastRunDateTime 存储在某个文件或数据库中,以便程序可以在恢复时从中断处继续。

于 2009-05-03T23:26:34.893 回答
1

System.Windows.Forms.Timer(或 System.Timers.Timer)

但是既然现在你说你不想使用定时器,你可以在另一个线程上运行一个轻量级的等待进程(检查时间,睡眠几秒钟,再次检查时间......)或制作一个引发事件的组件(使用轻量级等待过程)在某些预定时间或间隔

于 2008-11-21T04:00:26.277 回答
0

以下应该可以解决问题。

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
}
于 2020-10-15T09:47:04.787 回答
-1

我的目标是每晚 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();
}
于 2011-09-26T12:28:29.857 回答