每天在C#中运行一次

19
有没有巧妙的方法可以让我的executeEveryDayMethod()每天执行一次,而无需涉及Windows任务计划程序?

2
你的应用程序是否一直在运行?它是一个服务吗?你能多提供一些关于这些点的信息吗? - Robert Gould
12个回答

18
我通过以下方法实现了这一点...
  1. 设置一个计时器,每20分钟触发一次(实际时间可以根据您的需要进行调整-我需要在一天中多次运行)。
  2. 在每个Tick事件上,检查系统时间。将时间与您的方法的计划运行时间进行比较。
  3. 如果当前时间小于计划时间,请检查一些持久存储中的内容,以获取方法运行的上一次时间的日期时间值。
  4. 如果该方法上一次运行时间超过24小时,则运行该方法,并将此运行的日期时间存储到数据存储区中。
  5. 如果该方法上一次运行时间在最近24小时内,则忽略它。
希望对您有所帮助。
*编辑-C#代码示例::注意:未经测试...
using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Timer t1 = new Timer();
            t1.Interval = (1000 * 60 * 20); // 20 minutes...
            t1.Elapsed += new ElapsedEventHandler(t1_Elapsed);
            t1.AutoReset = true;
            t1.Start();

            Console.ReadLine();
        }

        static void t1_Elapsed(object sender, ElapsedEventArgs e)
        {
            DateTime scheduledRun = DateTime.Today.AddHours(3);  // runs today at 3am.
            System.IO.FileInfo lastTime = new System.IO.FileInfo(@"C:\lastRunTime.txt");
            DateTime lastRan = lastTime.LastWriteTime;
            if (DateTime.Now > scheduledRun)
            {
                TimeSpan sinceLastRun = DateTime.Now - lastRan;
                if (sinceLastRun.Hours > 23)
                {
                    doStuff();
                    // Don't forget to update the file modification date here!!!
                }
            }
        }

        static void doStuff()
        {
            Console.WriteLine("Running the method!");
        }
    }
}

计时器和经过时间的问题... 检查第一次运行的条件被排除了,或者在计时器开始计时之前没有调用doStuff()方法。如果等待24小时,您将会看到程序在一天内处于“死亡”状态。 - StingyJack
好的观点!正如我所说,它没有经过测试,并且我完全忘记了实现“第一次运行”的行为。我的错! - ZombieSheep

10

请看 quartz.net。它是用于 .net 的调度库。

更具体的内容请参见这里


2
经过两年多的开发、修复bug和添加新功能,Quartz.NET终于成熟到了1.0版本。我知道调度比起初看起来要困难得多。两年时间就为了一个克隆! - Vinko Vrsalovic
1
每个人都忘了提到它是完全免费的,没有GPL限制(Apache许可证:o)。 - wcm

4
如果运行时间不相关且可以在每次程序启动时重置,您可以设置一个计时器,这是最简单的方法。如果这不可接受,那么解决方案将变得更加复杂,例如在此处提出的解决方案仍无法解决持久性问题,如果您真正希望做到定期任务所能实现的功能,则需要单独解决该问题。我真的认为是否值得经历所有麻烦来复制一个完全良好的现有功能。

这里有一个相关问题(示例取自其中)。

using System;
using System.Timers;

public class Timer1
{
    private static Timer aTimer = new System.Timers.Timer(24*60*60*1000);

    public static void Main()
    {
        aTimer.Elapsed += new ElapsedEventHandler(ExecuteEveryDayMethod);
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program.");
        Console.ReadLine();
    }

    // Specify what you want to happen when the Elapsed event is 
    // raised.
    private static void ExecuteEveryDayMethod(object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

1
假设你在_Settings.DataCleanupTime中以"hh:mm:ss"的格式设置了每日时间。
  //note the namespace, there are 4 different timers in .NET
  System.Threading.Timer _Timer;

  DateTime now = DateTime.Now;
  //convert "hh:mm:ss" to three integers
  var dateparts = _Settings.DataCleanupTime.Split(new char[] { ':' }).Select(p => Convert.ToInt32(p)).ToArray();
  DateTime firstTime = new DateTime(now.Year, now.Month, now.Day, dateparts[0], dateparts[1], dateparts[2]);
  
  //e.g. firsttime is today at 2am and it is already 6am
  if(firstTime < now)
  {
    //first run will be tomorrow
    firstTime = firstTime.AddDays(1);
  }

  int delay = Convert.ToInt32((firstTime - now).TotalMilliseconds);

  _Timer = new Timer(DoWork, state:null, delay, 3600 * 24 * 1000);

DoWork的签名是:

public void DoWork(Object state)

要停止计时器,只需调用:

_Timer.Dispose();

请注意,这种使用固定24小时的解决方案(以及其他类似解决方案)在夏令时更改期间不会始终在相同时间触发。但我喜欢这个解决方案的简单性,只在需要时执行作业,而不是每20分钟轮询一次。 我稍微调整了一下,使得每次执行时都会重新计算下一个作业的时间,以避免这些夏令时问题。 - blue shell

1

公共部分类 Main :Form {
public Main( ) // Windows 窗体称为 Main { InitializeComponent( ); }

    private void Main_Load( object sender, EventArgs e )
    {
        /*
         This example uses a System.Windows.Forms Timer

         This code allows you to schedule an event at any given time in one day.
         In this example the timer will tick at 3AM.

         */
        Int32 alarm = GetAlarmInMilliseconds( 3, 0, 0 ); // Milliseconds until 3:00 am.
        timer_MessageCount.Interval = alarm; // Timer will tick at 3:00am.

        timer_MessageCount.Start( );                    
    }

    private Int32 GetAlarmInMilliseconds(Int32 eventHour, Int32 eventMinute, Int32 eventSecond )
    {
        DateTime now = DateTime.Now;
        DateTime eventTime = new DateTime( now.Year, now.Month, now.Day, eventHour, eventMinute, eventSecond );

        TimeSpan ts;

        if ( eventTime > now )
        {
            ts = eventTime - now;
        }
        else
        {
            eventTime = eventTime.AddDays( 1 );
            ts = eventTime - now;
        }

        Console.WriteLine("Next alarm in: {0}", ts );

        return ( Int32 ) ts.TotalMilliseconds;
    }        

    static void DoSomething( )
    {
        Console.WriteLine( "Run your code here." );
    }      

    private void timer_MessageCount_Tick( object sender, EventArgs e )
    {
        DoSomething( );

        Int32 alarm = GetAlarmInMilliseconds( 3, 0, 0 ); // Next alarm time = 3AM
        timer_MessageCount.Interval = alarm;            
    }
}

0

这是我非常简单的解决方案,用于每天执行一次方法:

private static DateTime _LastAccessedTime;
    
private static void OnceADayCode()  // method that you want access once a day
{
    _LastAccessedTime = DateTime.Today;
}

public static void PublicMethod()  // this can be a method called from outside
{
    if (_LastAccessedTime != DateTime.Today) 
    { 
        OnceADayCode(); 
    }
}

使用相同的逻辑,您也可以使用:

private static DateTime _LastAccessedTime;
    
private static void OnceADayCode()
{
    if (_LastAccessedTime.Today != DateTime.Today) 
    { 
        // code that you want access once a day
        _LastAccessedTime = DateTime.Today; 
    }
}

0

如果你在某个时间范围内查询时间并运行,即使机器关闭,你也会调用该方法或使用像Vinko建议的计时器。

但更好的解决方案(类似于旧版CRON,因此它是一种经过验证的模式)是具有一些持久数据,我现在能想到的最便宜的解决方案是一个空文件,检查其上次修改属性,如果它在过去24小时内没有被修改,则触摸它并运行您的方法。这样,您可以确保在应用程序例如周末停止运行的情况下首先运行该方法。

我以前在C#中做过这件事,但那是一年前在另一份工作中完成的,所以我没有代码,但大约有20行(包括所有注释)。


0
如果您只想每天运行一次且不关心时间,这将起作用(将在午夜后立即运行)。
声明一个 DateTime 变量:
DateTime _DateLastRun;

在你的启动程序中,设置初始日期值:
_DateLastRun = DateTime.Now.Date;

在逻辑区域中,您想要检查是否执行操作:

if (_DateLastRun < DateTime.Now.Date) 
{
    // Perform your action
    _DateLastRun= DateTime.Now.Date;
}

0
为了每天晚上7点到8点运行作业一次,我设置了一个间隔为3600000毫秒的计时器,然后只需执行以下代码即可进行计时器滴答。
private void timer1_Tick(object sender, EventArgs e)
{
    //ensure that it is running between 7-8pm daily.
    if (DateTime.Now.Hour == 19)
    { 
        RunJob(); 
    }
 }

对我来说,一个小时的时间窗口就可以了。如果需要更精细的时间间隔,则需要在计时器上设置更小的间隔(例如一分钟的60000毫秒),并在条件语句中包含分钟。

例如:

{
    //ensure that it is running at 7:30pm daily.
    if (DateTime.Now.Hour == 19 && DateTime.Now.Minute == 30)
    { 
        RunJob(); 
    }
 }

1
你必须在RunJob()中添加Thread.Sleep(60 * 1000),否则您将会有多个RunJob()实例,每秒钟或更频繁一次。 - Fred Smith

0

如果您正在运行 Windows Forms 应用程序,以下是如何执行操作的方法。但您需要配置一个设置以便可以存储事件最后触发的日期。如果您从不打算关闭应用程序,可以将日期存储为静态值。

我正在使用计时器来触发事件,如下所示:

        private void tmrAutoBAK_Tick(object sender, EventArgs e)
        {
            if (BakDB.Properties.Settings.Default.lastFireDate != DateTime.Now.ToString("yyyy-MM-dd"))
            {
                 tmrAutoBAK.Stop(); //STOPS THE TIMER IN CASE OF EVENTUAL MESSAGEBOXES.
                 createBakup(); //EVENT
                 BakDB.Properties.Settings.Default.lastFireDate = DateTime.Now.ToString("yyyy-MM-dd"); //STORING CURRENT DATE TO SETTINGS FILE.
                 BakDB.Properties.Settings.Default.Save(); //SAVING THE SETTING FILE.
                 tmrAutoBAK.Start(); //RESTARTING TIMER
            }
        }

你应该使用 DateTime.UtcNow 来避免夏令时问题。 - Orace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接