【问题标题】:How to call a method daily, at specific time, in C#?如何在 C# 中每天在特定时间调用方法?
【发布时间】:2011-03-15 16:11:23
【问题描述】:

我在 SO 上进行了搜索,并找到了有关 Quartz.net 的答案。但这对我的项目来说似乎太大了。我想要一个等效的解决方案,但更简单并且(充其量)在代码中(不需要外部库)。如何每天在特定时间调用方法?

我需要添加一些关于此的信息:

  • 最简单(也是丑陋)的方法是每秒/分钟检查一次时间并在正确的时间调用该方法

我想要一种更有效的方法来做到这一点,无需不断检查时间,并且我可以控制工作是否完成。如果方法失败(由于任何问题),程序应该知道写入日志/发送电子邮件。这就是为什么我需要调用一个方法,而不是安排一个作业。

我在 Java 中找到了这个解决方案 Call a method at fixed time in Java。 C#中是否有类似的方式?

编辑:我已经做到了。我在 void Main() 中添加了一个参数,并创建了一个 bat(由 Windows 任务计划程序调度)来使用此参数运行程序。程序运行,完成工作,然后退出。如果作业失败,它能够写入日志和发送电子邮件。这种方法很符合我的要求:)

【问题讨论】:

  • 那个链接的问题似乎表明您正在运行的应用程序中的一个方法必须定期调用。是这样吗?这将影响您是否需要进程内调度或是否可以只使用 Windows 调度程序。
  • 我的程序将根据要求连续运行
  • 嘿,我不敢相信你称我的回答“丑陋”。他们在战斗的话:-)
  • 不是说你的答案:p。我也想过,我觉得我的丑。

标签: c# winforms methods scheduled-tasks


【解决方案1】:
  • 创建一个控制台应用程序来满足您的需求
  • 使用 Windows“Scheduled Tasks”功能让该控制台应用在您需要运行时执行

这就是你真正需要的!

更新:如果您想在应用中执行此操作,您有多种选择:

  • Windows 窗体 应用程序中,您可以点击 Application.Idle 事件并检查您是否已到达当天调用您的方法的时间。仅当您的应用程序不忙于其他事情时才调用此方法。快速检查一下是否已达到您的目标时间不应该对您的应用造成太大压力,我认为...
  • 在 ASP.NET Web 应用程序中,有一些方法可以“模拟”发送预定事件 - 看看这个CodeProject article
  • 当然,您也可以在任何 .NET 应用程序中简单地“自行开发” - 查看 CodeProject article 以获取示例实现

更新#2:如果你想每 60 分钟检查一次,你可以创建一个每 60 分钟唤醒一次的计时器,如果时间到了,它会调用该方法。

类似这样的:

using System.Timers;

const double interval60Minutes = 60 * 60 * 1000; // milliseconds to one hour

Timer checkForTime = new Timer(interval60Minutes);
checkForTime.Elapsed += new ElapsedEventHandler(checkForTime_Elapsed);
checkForTime.Enabled = true;

然后在您的事件处理程序中:

void checkForTime_Elapsed(object sender, ElapsedEventArgs e)
{
    if (timeIsReady())
    {
       SendEmail();
    }
}

【讨论】:

  • 之前想过。 :)。但是我的程序会连续运行,如果有的话我想知道另一种方法:)
  • 这是一个 Winform 应用程序。我会尝试说服我的老板改变它的设计,但首先我应该尝试满足他的要求:p
  • timeIsReady() 调用有什么作用?
  • @brimble2010:它可以做任何你想做的事情。你可以例如有一张临时“阻塞”时间段的桌子(例如不要在凌晨 3 点、4 点或 5 点运行)或其他什么 - 完全取决于你。除了每 60 分钟启动一次之外,只需进行一次额外检查 ....
  • 非常好的、简单、简短和干净的解决方案,直到今天在 Windows 10 和 .Net 4.7.2 中运行良好。!!谢谢。
【解决方案2】:

我创建了一个简单易用的调度程序,您不需要使用外部库。 TaskScheduler 是一个单例,它在计时器上保留引用,因此计时器不会被垃圾收集,它可以调度多个任务。您可以设置第一次运行(小时和分钟),如果在调度时这个时间超过调度在第二天这个时间开始。但是自定义代码很容易。

安排新任务非常简单。示例:在 11:52,第一个任务是每 15 秒,第二个示例是每 5 秒。对于每日执行,将 24 设置为 3 参数。

TaskScheduler.Instance.ScheduleTask(11, 52, 0.00417, 
    () => 
    {
        Debug.WriteLine("task1: " + DateTime.Now);
        //here write the code that you want to schedule
    });

TaskScheduler.Instance.ScheduleTask(11, 52, 0.00139,
    () =>
    {
        Debug.WriteLine("task2: " + DateTime.Now);
        //here write the code that you want to schedule
    });

我的调试窗口:

task2: 07.06.2017 11:52:00
task1: 07.06.2017 11:52:00
task2: 07.06.2017 11:52:05
task2: 07.06.2017 11:52:10
task1: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:15
task2: 07.06.2017 11:52:20
task2: 07.06.2017 11:52:25
...

只需将此类添加到您的项目中:

public class TaskScheduler
{
    private static TaskScheduler _instance;
    private List<Timer> timers = new List<Timer>();

    private TaskScheduler() { }

    public static TaskScheduler Instance => _instance ?? (_instance = new TaskScheduler());

    public void ScheduleTask(int hour, int min, double intervalInHour, Action task)
    {
        DateTime now = DateTime.Now;
        DateTime firstRun = new DateTime(now.Year, now.Month, now.Day, hour, min, 0, 0);
        if (now > firstRun)
        {
            firstRun = firstRun.AddDays(1);
        }

        TimeSpan timeToGo = firstRun - now;
        if (timeToGo <= TimeSpan.Zero)
        {
            timeToGo = TimeSpan.Zero;
        }

        var timer = new Timer(x =>
        {
            task.Invoke();
        }, null, timeToGo, TimeSpan.FromHours(intervalInHour));

        timers.Add(timer);
    }
}

【讨论】:

  • new Timer 没有采用 4 个参数的方法。
  • 我假设这里的 Timer 来自 System.Timers?你能提供一个示例工作程序吗?谢谢
  • @Fandango68 我使用了来自命名空间 System.Threading 的计时器。 System.Timers 中还有另一个 Timer。我认为您在顶部使用了 false using。
  • @jannagy02 如何为星期一等特定日期安排任务?
【解决方案3】:

每当我构建需要此类功能的应用程序时,我总是通过我发现的一个简单的 .NET 库来使用 Windows 任务计划程序

see my answer to a similar question获取一些示例代码和更多解释。

【讨论】:

  • 此库仅适用于 Windows 操作系统,如果您需要在 linux 或其他操作系统上运行,请不要在您的 .NET Core 项目中使用它。
【解决方案4】:

正如其他人所说,您可以使用控制台应用程序按计划运行。其他人没有说的是,您可以此应用程序触发您在主应用程序中等待的跨进程 EventWaitHandle。

控制台应用:

class Program
{
    static void Main(string[] args)
    {
        EventWaitHandle handle = 
            new EventWaitHandle(true, EventResetMode.ManualReset, "GoodMutexName");
        handle.Set();
    }
}

主应用:

private void Form1_Load(object sender, EventArgs e)
{
    // Background thread, will die with application
    ThreadPool.QueueUserWorkItem((dumby) => EmailWait());
}

private void EmailWait()
{
    EventWaitHandle handle = 
        new EventWaitHandle(false, EventResetMode.ManualReset, "GoodMutexName");

    while (true)
    {
        handle.WaitOne();

        SendEmail();

        handle.Reset();
    }
}

【讨论】:

    【解决方案5】:

    这是使用 TPL 执行此操作的一种方法。无需创建/处置计时器等:

    void ScheduleSomething()
    {
    
        var runAt = DateTime.Today + TimeSpan.FromHours(16);
    
        if (runAt <= DateTime.Now)
        {
            DoSomething();
        }
        else
        {
            var delay = runAt - DateTime.Now;
            System.Threading.Tasks.Task.Delay(delay).ContinueWith(_ => DoSomething());
        }
    
    }
    
    void DoSomething()
    {
        // do somethig
    }
    

    【讨论】:

      【解决方案6】:

      据我所知,可能也是最简单的最好方法是使用 Windows 任务计划程序在一天中的特定时间执行您的代码,或者让您的应用程序永久运行并检查一天中的特定时间或编写 Windows 服务那也是一样的。

      【讨论】:

        【解决方案7】:

        我知道这是旧的,但如何:

        构建一个在启动时触发的计时器,用于计算下一次运行的时间。在运行时的第一次调用中,取消第一个计时器并启动一个新的每日计时器。每天更改为每小时或任何您想要的周期性。

        【讨论】:

        • 并在夏令时更改期间观看它失败。 . .
        【解决方案8】:

        这个小程序应该是解决方案;-)

        希望对大家有所帮助。

        using System;
        using System.Threading;
        using System.Threading.Tasks;
        
        namespace DailyWorker
        {
            class Program
            {
                static void Main(string[] args)
                {
                    var cancellationSource = new CancellationTokenSource();
        
                    var utils = new Utils();
                    var task = Task.Run(
                        () => utils.DailyWorker(12, 30, 00, () => DoWork(cancellationSource.Token), cancellationSource.Token));
        
                    Console.WriteLine("Hit [return] to close!");
                    Console.ReadLine();
        
                    cancellationSource.Cancel();
                    task.Wait();
                }
        
                private static void DoWork(CancellationToken token)
                {
                    while (!token.IsCancellationRequested)
                    {
                        Console.Write(DateTime.Now.ToString("G"));
                        Console.CursorLeft = 0;
                        Task.Delay(1000).Wait();
                    }
                }
            }
        
            public class Utils
            {
                public void DailyWorker(int hour, int min, int sec, Action someWork, CancellationToken token)
                {
                    while (!token.IsCancellationRequested)
                    {
                        var dateTimeNow = DateTime.Now;
                        var scanDateTime = new DateTime(
                            dateTimeNow.Year,
                            dateTimeNow.Month,
                            dateTimeNow.Day,
                            hour,       // <-- Hour when the method should be started.
                            min,  // <-- Minutes when the method should be started.
                            sec); // <-- Seconds when the method should be started.
        
                        TimeSpan ts;
                        if (scanDateTime > dateTimeNow)
                        {
                            ts = scanDateTime - dateTimeNow;
                        }
                        else
                        {
                            scanDateTime = scanDateTime.AddDays(1);
                            ts           = scanDateTime - dateTimeNow;
                        }
        
                        try
                        {
                             Task.Delay(ts).Wait(token);
                        }
                        catch (OperationCanceledException)
                        {
                            break;
                        }
        
                        // Method to start
                        someWork();
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          我最近刚刚编写了一个必须每天重新启动的 C# 应用程序。我意识到这个问题很老,但我认为添加另一个可能的解决方案并没有什么坏处。这就是我在指定时间处理每日重启的方式。

          public void RestartApp()
          {
            AppRestart = AppRestart.AddHours(5);
            AppRestart = AppRestart.AddMinutes(30);
            DateTime current = DateTime.Now;
            if (current > AppRestart) { AppRestart = AppRestart.AddDays(1); }
          
            TimeSpan UntilRestart = AppRestart - current;
            int MSUntilRestart = Convert.ToInt32(UntilRestart.TotalMilliseconds);
          
            tmrRestart.Interval = MSUntilRestart;
            tmrRestart.Elapsed += tmrRestart_Elapsed;
            tmrRestart.Start();
          }
          

          为确保您的计时器保持在范围内,我建议使用System.Timers.Timer tmrRestart = new System.Timers.Timer() 方法在该方法之外创建它。将方法RestartApp() 放入表单加载事件中。当应用程序启动时,它将设置 AppRestart 的值,如果 current 大于重启时间,我们将 1 天添加到 AppRestart 以确保按时重启,并且我们不会因为放置负值进入定时器。在tmrRestart_Elapsed 事件中运行您需要在该特定时间运行的任何代码。如果您的应用程序自行重新启动,您不一定要停止计时器,但这也没有什么坏处,如果应用程序没有重新启动,只需再次调用 RestartApp() 方法,您就可以开始了。

          【讨论】:

            【解决方案10】:

            如果要运行可执行文件,请使用 Windows 计划任务。我会假设(可能是错误的)你想要一个方法在你当前的程序中运行。

            为什么不让一个线程连续运行来存储调用该方法的最后日期?

            让它每分钟唤醒一次(例如),如果当前时间大于指定时间并且最后存储的日期不是当前日期,则调用该方法然后更新日期。

            【讨论】:

              【解决方案11】:

              可能只是我,但似乎这些答案中的大多数都不完整或无法正常工作。我做了一些非常快速和肮脏的东西。话虽这么说,但不确定这样做的想法有多好,但每次都能完美运行。

              while (true)
              {
                  if(DateTime.Now.ToString("HH:mm") == "22:00")
                  {
                      //do something here
                      //ExecuteFunctionTask();
                      //Make sure it doesn't execute twice by pausing 61 seconds. So that the time is past 2200 to 2201
                      Thread.Sleep(61000);
                  }
              
                  Thread.Sleep(10000);
              }
              

              【讨论】:

              【解决方案12】:

              我发现这非常有用:

              using System;
              using System.Timers;
              
              namespace ScheduleTimer
              {
                  class Program
                  {
                      static Timer timer;
              
                      static void Main(string[] args)
                      {
                          schedule_Timer();
                          Console.ReadLine();
                      }
              
                      static void schedule_Timer()
                      {
                          Console.WriteLine("### Timer Started ###");
              
                          DateTime nowTime = DateTime.Now;
                          DateTime scheduledTime = new DateTime(nowTime.Year, nowTime.Month, nowTime.Day, 8, 42, 0, 0); //Specify your scheduled time HH,MM,SS [8am and 42 minutes]
                          if (nowTime > scheduledTime)
                          {
                              scheduledTime = scheduledTime.AddDays(1);
                          }
              
                          double tickTime = (double)(scheduledTime - DateTime.Now).TotalMilliseconds;
                          timer = new Timer(tickTime);
                          timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
                          timer.Start();
                      }
              
                      static void timer_Elapsed(object sender, ElapsedEventArgs e)
                      {
                          Console.WriteLine("### Timer Stopped ### \n");
                          timer.Stop();
                          Console.WriteLine("### Scheduled Task Started ### \n\n");
                          Console.WriteLine("Hello World!!! - Performing scheduled task\n");
                          Console.WriteLine("### Task Finished ### \n\n");
                          schedule_Timer();
                      }
                  }
              }
              

              【讨论】:

                【解决方案13】:

                尝试使用 Windows 任务计划程序。创建一个不提示任何用户输入的 exe。

                https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page

                【讨论】:

                  【解决方案14】:

                  3 班轮怎么样?

                          DateTime startTime = DateTime.Today.AddDays(1).AddHours(8).AddMinutes(30); // Today starts at midnight, so add the number of days, hours and minutes until the desired start time, which in this case is the next day at 8:30 a.m.
                          TimeSpan waitFor = startTime - DateTime.Now; // Calcuate how long it is until the start time
                          await Task.Delay(waitFor); // Wait until the start time
                  

                  【讨论】:

                  • 这个答案不会为已经存在的答案添加任何新想法
                  【解决方案15】:

                  您可以计算剩余时间并将计时器设置为该时间的一半(或其他分数),而不是设置每 60 分钟每秒运行一次的时间。这样一来,您就不必过多地检查时间,而且还可以保持一定程度的准确性,因为计时器间隔会减少您越接近目标时间。

                  例如,如果您想在 60 分钟后做某事,则计时器间隔将是近似的:

                  30:00:00, 15:00:00, 07:30:00, 03:45:00, ... , 00:00:01,快跑!

                  我使用下面的代码每天自动重启一次服务。我使用线程是因为我发现定时器在很长一段时间内都不可靠,虽然在这个例子中成本更高,但它是唯一为此目的创建的,所以这无关紧要。

                  (从 VB.NET 转换而来)

                  autoRestartThread = new System.Threading.Thread(autoRestartThreadRun);
                  autoRestartThread.Start();
                  

                  ...

                  private void autoRestartThreadRun()
                  {
                      try {
                          DateTime nextRestart = DateAndTime.Today.Add(CurrentSettings.AutoRestartTime);
                          if (nextRestart < DateAndTime.Now) {
                              nextRestart = nextRestart.AddDays(1);
                          }
                  
                          while (true) {
                              if (nextRestart < DateAndTime.Now) {
                                  LogInfo("Auto Restarting Service");
                                  Process p = new Process();
                                  p.StartInfo.FileName = "cmd.exe";
                                  p.StartInfo.Arguments = string.Format("/C net stop {0} && net start {0}", "\"My Service Name\"");
                                  p.StartInfo.LoadUserProfile = false;
                                  p.StartInfo.UseShellExecute = false;
                                  p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                                  p.StartInfo.CreateNoWindow = true;
                                  p.Start();
                              } else {
                                  dynamic sleepMs = Convert.ToInt32(Math.Max(1000, nextRestart.Subtract(DateAndTime.Now).TotalMilliseconds / 2));
                                  System.Threading.Thread.Sleep(sleepMs);
                              }
                          }
                      } catch (ThreadAbortException taex) {
                      } catch (Exception ex) {
                          LogError(ex);
                      }
                  }
                  

                  请注意,我已将最小间隔设置为 1000 毫秒,这可以根据您需要的精度增加、减少或删除。

                  记得在你的应用程序关闭时停止你的线程/定时器。

                  【讨论】:

                    【解决方案16】:

                    我有一个简单的方法。这会在操作发生之前产生 1 分钟的延迟。您也可以添加秒数来制作 Thread.Sleep();更短。

                    private void DoSomething(int aHour, int aMinute)
                    {
                        bool running = true;
                        while (running)
                        {
                            Thread.Sleep(1);
                            if (DateTime.Now.Hour == aHour && DateTime.Now.Minute == aMinute)
                            {
                                Thread.Sleep(60 * 1000); //Wait a minute to make the if-statement false
                                //Do Stuff
                            }
                        }
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      24 小时时间

                      var DailyTime = "16:59:00";
                                  var timeParts = DailyTime.Split(new char[1] { ':' });
                      
                                  var dateNow = DateTime.Now;
                                  var date = new DateTime(dateNow.Year, dateNow.Month, dateNow.Day,
                                             int.Parse(timeParts[0]), int.Parse(timeParts[1]), int.Parse(timeParts[2]));
                                  TimeSpan ts;
                                  if (date > dateNow)
                                      ts = date - dateNow;
                                  else
                                  {
                                      date = date.AddDays(1);
                                      ts = date - dateNow;
                                  }
                      
                                  //waits certan time and run the code
                                  Task.Delay(ts).ContinueWith((x) => OnTimer());
                      
                      public void OnTimer()
                          {
                              ViewBag.ErrorMessage = "EROOROOROROOROR";
                          }
                      

                      【讨论】:

                        【解决方案18】:

                        一个任务的简单示例:

                        using System;
                        using System.Timers;
                        
                        namespace ConsoleApp
                        {
                            internal class Scheduler
                            {
                                private static readonly DateTime scheduledTime = 
                                    new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 10, 0, 0);
                                private static DateTime dateTimeLastRunTask;
                        
                                internal static void CheckScheduledTask()
                                {
                                    if (dateTimeLastRunTask.Date < DateTime.Today && scheduledTime.TimeOfDay < DateTime.Now.TimeOfDay)
                                    {
                                        Console.WriteLine("Time to run task");
                                        dateTimeLastRunTask = DateTime.Now;
                                    }
                                    else
                                    {
                                        Console.WriteLine("not yet time");
                                    }
                                }
                            }
                        
                            internal class Program
                            {
                                private static Timer timer;
                        
                                static void Main(string[] args)
                                {
                                    timer = new Timer(5000);
                                    timer.Elapsed += OnTimer;
                                    timer.Start();
                                    Console.ReadLine();
                                }
                        
                                private static void OnTimer(object source, ElapsedEventArgs e)
                                {
                                    Scheduler.CheckScheduledTask();
                                }
                            }
                        }
                        

                        【讨论】:

                        • 这看起来不像是计时器,而是看起来像一个间隔,不准确。
                        • 定时器开始时间设置在一个变量 scheduleTime 中。方法 Console.WriteLine("Time to run task") 会在每天这个时候运行。
                        • 感谢您的解释。
                        【解决方案19】:

                        使用 System.Threading.Timer 的解决方案:

                            private void nameOfMethod()
                            {
                                //do something
                            }
                        
                            /// <summary>
                            /// run method at 22:00 every day
                            /// </summary>
                            private void runMethodEveryDay()
                            {
                                var runAt = DateTime.Today + TimeSpan.FromHours(22);
                        
                                if(runAt.Hour>=22)
                                    runAt = runAt.AddDays(1.00d); //if aplication is started after 22:00 
                        
                                var dueTime = runAt - DateTime.Now; //time before first run ; 
                        
                                long broj3 = (long)dueTime.TotalMilliseconds;
                                TimeSpan ts2 = new TimeSpan(24, 0, 1);//period of repeating method
                                long broj4 = (long)ts2.TotalMilliseconds;
                                timer2 = new System.Threading.Timer(_ => nameOfMethod(), null, broj3, broj4);
                            }
                        

                        【讨论】:

                          猜你喜欢
                          • 2013-05-15
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-01-28
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多