如何在.NET中每小时(或每小时的特定时间间隔)引发事件?

时间:2008-11-21 03:59:16

标签: c# .net scheduled-tasks

我正在开发一个小型网络抓取工具,它将在系统托盘中运行,并且每小时每小时抓取一个网站。

让.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);
}

7 个答案:

答案 0 :(得分:31)

System.Timers.Timer。如果你想在一天中的特定时间运行,你需要确定它到下一次的时间,并将其设置为你的间隔。

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

int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60 * 1000;  

答案 1 :(得分:11)

您可以从Quartz.net http://quartznet.sourceforge.net/

找到帮助

答案 2 :(得分: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);
        }            
    }

答案 3 :(得分:2)

另一种策略是记录流程运行的最后时间,并确定自那时起是否已经过了所需的时间间隔。在此策略中,如果经过的时间等于或大于所需的时间间隔,则会对事件进行编码。通过这种方式,您可以处理如果由于某种原因计算机出现故障而可能错过长间隔(例如,每天一次)的实例。

例如:

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

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

答案 4 :(得分:1)

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

但是从现在开始你说你不想使用Timers,你可以在另一个线程上运行一个轻量级的等待进程(检查时间,睡几秒钟,再次检查时间......)或者创建一个提升一个组件的组件某些预定时间或间隔的事件(使用轻量级等待过程)

答案 5 :(得分: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
}

答案 6 :(得分:-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();
}