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


83

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

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

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

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

我发现此解决方案在Java固定时间在Java调用方法。C#中有类似的方法吗?

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


2
这个链接的问题似乎表明您正在运行的应用程序中的方法必须定期调用。是这样吗 这将影响您是否需要进程内调度,或者是否只能使用Windows调度程序。
paxdiablo

我的程序将根据需要连续运行
Quan Mai 2010年

嘿,我简直不敢相信您将我的回答称为“丑陋”。他们打架的话:-)
paxdiablo

不是故意要回答你的:p。我也考虑过,发现我很丑。
全麦

Answers:


85
  • 创建一个可以满足您需求的控制台应用
  • 使用Windows的“计划任务”功能在需要运行该控制台应用程序时执行该控制台应用程序

这真的就是您所需要的!

更新:如果要在应用程序中执行此操作,则有几种选择:

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

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

之前考虑过。:)。但是我的程序将连续运行,如果有的话,我想知道另一种方法:)
Quan Mai 2010年

这是一个Winform应用程序。我将尝试说服老板更改设计,但首先我应该尝试满足他的要求:p
Quan Mai 2010年

4
这个timeIsReady()电话做什么?
brimble2010 2015年

1
@ brimble2010:它可以做任何您喜欢做的事。例如,您可能有一个带有临时“阻塞”时隙的表(例如,不要在凌晨3点,4点或5点运行)或其他任何东西-完全由您决定。仅每60分钟就会发出一次额外的支票....
marc_s 2015年

1
非常好,简单,简短且干净的解决方案,在Windows 10和.Net 4.7.2。中一直有效到今天!谢谢。
6

19

我创建了一个简单的调度程序,该调度程序易于使用,您不需要使用外部库。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个参数的方法。
Fandango68 '18

我认为这里的计时器来自System.Timers?您能否提供示例工作程序?谢谢
Fandango68 '18

1
@ Fandango68我已经使用了命名空间System.Threading中的Timer。System.Timers中还有另一个Timer。我认为您在顶部使用了false用法。
jannagy02 '18

@ jannagy02如何安排特定日期(如星期一)的任务?
Sruthi Varghese


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

4

我知道的最好的方法,也许是最简单的方法,是使用Windows Task Scheduler在一天的特定时间执行代码,或者让您的应用程序永久运行并检查一天中的特定时间,或者编写执行以下操作的Windows服务:相同。


4

我知道这很老了,但是呢:

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


9
并注意它在夏时制更改期间会失败。。。
Jim Mischel

3

这是使用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
}

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

1

如果要运行可执行文件,请使用Windows计划任务。我将假设(可能错误地)假设您希望在当前程序中运行一种方法。

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

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


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

1
据我所知,这将是一个繁忙的等待,因此不是一个好主意。en.wikipedia.org/wiki/Busy_waiting
汤米·伊瓦森

1

我刚刚写了一个ac#应用,该应用必须每天重启。我知道这个问题已经过时,但是我认为添加另一个可能的解决方案没有什么坏处。这就是我在指定时间处理每日重启的方式。

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()method在方法之外创建计时器。将方法放入RestartApp()表单加载事件中。当应用程序启动时,它将为AppRestartif设置current大于重新启动时间的值,我们增加了1天AppRestart以确保重新启动按时进行,并且不会为计时器添加负值而出现异常。在tmrRestart_Elapsed事件中运行任何代码,你需要跑在那个特定的时间。如果您的应用程序自行重启,则不必停止计时器,但也不会造成任何伤害。如果应用程序未重启,则只需RestartApp()再次调用该方法,您就可以进行了。


1

我发现这非常有用:

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


0

不必设置每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毫秒,根据您的要求,该间隔可以增加,减少或删除。

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


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

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

0

一个任务的简单示例:

using System;
using System.Timers;

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

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

-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);
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.