如何在C#的特定时间每天调用一个方法?

时间:2010-07-14 04:14:29

标签: c# winforms methods scheduled-tasks

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

我需要添加一些有关此内容的信息:

  • 最简单(也很丑陋)的方法是检查每秒/分钟的时间并在适当的时候调用方法

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

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

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

19 个答案:

答案 0 :(得分:71)

  • 创建一个控制台应用程序,用于执行您正在寻找的内容
  • 使用Windows“Scheduled Tasks”功能让控制台应用程序在您需要运行时执行

这就是你真正需要的一切!

更新:如果您想在自己的应用中执行此操作,可以选择以下几种方法:

  • Windows窗体应用中,您可以点按Application.Idle事件并检查是否已达到当天的时间来调用您的方法。仅当您的应用不忙于其他内容时才会调用此方法。快速检查你的目标时间是否已经达到,不应该给你的应用带来太多压力,我想......
  • 在ASP.NET网络应用中,有“模拟”发送预定事件的方法 - 请查看此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();
    }
}

答案 1 :(得分:9)

每当我构建需要此类功能的应用程序时,我始终通过我找到的简单.NET库使用Windows任务计划程序。

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

答案 2 :(得分:8)

正如其他人所说,您可以使用控制台应用程序在计划时运行。其他人没有说的是你可以通过这个应用程序触发你在主应用程序中等待的跨进程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();
    }
}

答案 3 :(得分:6)

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

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

txtsub.Text

我的调试窗口:

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
...

答案 4 :(得分:4)

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

答案 5 :(得分:4)

我知道这是旧的,但是这个怎么样:

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

答案 6 :(得分:2)

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

我希望这对所有人都有帮助。

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();
            }
        }
    }
}

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:1)

我刚刚写了一个必须每天重启的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()方法,您就可以开始使用了。< / p>

答案 9 :(得分:1)

可能只是我,但似乎大多数答案都不完整或无法正常工作。我做了很快又脏的东西。据说不确定这样做有多好,但每次都能完美运作。

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

答案 10 :(得分:1)

尝试使用Windows Task Scheduler。创建一个不提示任何用户输入的exe。

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

答案 11 :(得分:0)

这是我最近写的:

void Main()
{
    // executes the action every interval by picking execution time from start of the hour instead of arbitrary start time.
    // For example, when inteval is 2, executes at 0, 2, 4, 6th minute of the hour
    Console.WriteLine($"started at: {DateTimeOffset.UtcNow}");
    this.RunPeriodically(intervalMins: 2, action: () => { Console.WriteLine("executed at: " + DateTimeOffset.UtcNow); }).GetAwaiter().GetResult();
}

public async Task RunPeriodically(int intervalMins, Action action)
{
    await this.FirstExecutionDelay(intervalMins);

    while (true)
    {
        action();
        await Task.Delay(TimeSpan.FromMinutes(intervalMins));
    }
}

public async Task FirstExecutionDelay(int intervalMin)
{
    var time = DateTimeOffset.UtcNow;
    List<int> mins = Enumerable.Range(0, 60).Where(min => min % intervalMin == 0).ToList();
    
    var closestList = mins
        .OrderBy(min => Math.Abs(time.Minute - min))
        .ThenByDescending(min => min)
        .ToList();
    int nextIntervalMin = closestList[0] == time.Minute ? closestList[1] : closestList[0];
    var nextExecutionTime = new DateTimeOffset(time.Year, time.Month, time.Day, time.Hour, nextIntervalMin, 0, TimeSpan.Zero);
    
    await Task.Delay(nextExecutionTime - time);
}

这是一个例子,它在任意时间开始,但恰好在最小边界处执行。由于它从一小时开始考虑分钟,因此它可以在多台机器上运行并且所有机器都可以完全同时执行:

enter image description here

答案 12 :(得分:0)

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

答案 13 :(得分:0)

一个任务的简单示例:

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();
        }
    }
}

答案 14 :(得分:0)

我发现这非常有用:

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();
        }
    }
}

答案 15 :(得分:0)

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";
    }

答案 16 :(得分:0)

我有一个简单的方法。这会在动作发生之前产生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 :(得分:0)

您可以计算剩余时间并将计时器设置为此时间的一半(或其他一部分),而不是设置每60分钟每秒运行一次的时间。这样你就不会多花时间检查时间,但也可以保持一定程度的适应性,因为定时器间隔会缩短你到目标时间的距离。

例如,如果你想在60分钟后做一些事情,那么定时器的间隔就会很明显:​​

30:00:00,15:00:00,07:30:00,03:45:00,...,00:00:01,RUN!

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

(从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毫秒的最小间隔,根据您需要的灵敏度,可以增加,减少或删除。

请记住在应用程序关闭时也停止线程/计时器。

答案 18 :(得分:-1)

使用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);
    }