Làm thế nào để gọi một phương thức hàng ngày, tại thời điểm cụ thể, trong C #?


83

Tôi đã tìm kiếm trên SO và tìm thấy câu trả lời về Quartz.net. Nhưng nó dường như là quá lớn cho dự án của tôi. Tôi muốn một giải pháp tương đương, nhưng đơn giản hơn và (tốt nhất) trong mã (không cần thư viện bên ngoài). Làm cách nào để gọi một phương thức hàng ngày, vào một thời điểm cụ thể?

Tôi cần thêm một số thông tin về điều này:

  • cách đơn giản nhất (và xấu nhất) để làm điều này, là kiểm tra thời gian mỗi giây / phút và gọi phương thức, vào đúng thời điểm

Tôi muốn một cách hiệu quả hơn để làm điều này, không cần phải kiểm tra thời gian liên tục và tôi có thể kiểm soát xem công việc có hoàn thành hay không. Nếu phương pháp không thành công (vì bất kỳ vấn đề nào), chương trình sẽ biết để ghi nhật ký / gửi email. Đó là lý do tại sao tôi cần gọi một phương pháp chứ không phải lên lịch công việc.

Tôi đã tìm thấy giải pháp này Gọi một phương thức vào thời điểm cố định trong Java trong Java. Có cách nào tương tự trong C # không?

CHỈNH SỬA: Tôi đã làm điều này. Tôi đã thêm một tham số vào void Main () và tạo một con dơi (được lập lịch bởi Windows Task Scheduler) để chạy chương trình với tham số này. Chương trình chạy, thực hiện công việc và sau đó thoát. Nếu một công việc không thành công, nó có khả năng viết nhật ký và gửi email. Cách tiếp cận này phù hợp với yêu cầu của tôi :)


2
Câu hỏi được liên kết đó dường như chỉ ra rằng một phương thức trong ứng dụng đang chạy của bạn phải được gọi định kỳ. Đó là trường hợp? Nó sẽ ảnh hưởng đến việc bạn cần lập lịch trong quá trình hay bạn chỉ có thể sử dụng bộ lập lịch của Windows.
paxdiablo

chương trình của tôi, theo yêu cầu, sẽ chạy liên tục
Quan Mai

Này, tôi không thể tin rằng bạn đã gọi câu trả lời của tôi là "xấu xí". Them's fightin 'words :-)
paxdiablo

Không có ý về câu trả lời của bạn: p. Tôi cũng nghĩ về điều đó và tôi thấy mình thật xấu xí.
Quán Mai

Được liên kết đến stackoverflow.com/questions/725917
PaulB,

Câu trả lời:


85
  • Tạo một ứng dụng bảng điều khiển thực hiện những gì bạn đang tìm kiếm
  • Sử dụng chức năng " Tác vụ đã lên lịch " của Windows để ứng dụng bảng điều khiển đó được thực thi vào thời điểm bạn cần nó chạy

Đó thực sự là tất cả những gì bạn cần!

Cập nhật: nếu bạn muốn thực hiện việc này bên trong ứng dụng của mình, bạn có một số tùy chọn:

  • trong ứng dụng Windows Forms , bạn có thể nhấn vào Application.Idlesự kiện và kiểm tra xem liệu bạn đã đến thời điểm trong ngày để gọi phương thức của mình hay chưa. Phương thức này chỉ được gọi khi ứng dụng của bạn không bận với những thứ khác. Kiểm tra nhanh để xem liệu bạn đã đạt được thời gian mục tiêu hay chưa sẽ không gây quá nhiều căng thẳng cho ứng dụng của bạn, tôi nghĩ ...
  • trong ứng dụng web ASP.NET, có các phương pháp "mô phỏng" việc gửi các sự kiện đã lên lịch - hãy xem bài viết CodeProject này
  • và tất nhiên, bạn cũng có thể chỉ cần "tự triển khai" trong bất kỳ ứng dụng .NET nào - hãy xem bài viết CodeProject này để biết cách triển khai mẫu

Cập nhật # 2 : nếu bạn muốn kiểm tra 60 phút một lần, bạn có thể tạo một bộ hẹn giờ thức dậy sau mỗi 60 phút và nếu hết thời gian, nó sẽ gọi phương thức.

Một cái gì đó như thế này:

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;

và sau đó trong trình xử lý sự kiện của bạn:

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

Đã nghĩ về nó trước đây. :). Nhưng chương trình của tôi sẽ chạy liên tục, và tôi muốn biết một cách khác, nếu có :)
Quan Mai

đó là một ứng dụng Winform. Tôi sẽ cố gắng thuyết phục ông chủ của tôi để thay đổi nó thiết kế, nhưng lúc đầu tôi nên cố gắng để điền vào yêu cầu của mình: p
Quân Mai

4
Không những gì timeIsReady()gọi làm gì?
brimble2010

1
@ brimble2010: nó có thể làm bất cứ điều gì bạn thích nó làm. Ví dụ, bạn có thể có một bàn với các khoảng thời gian "bị chặn" tạm thời (ví dụ: không chạy lúc 3, 4 hoặc 5 giờ sáng ) hoặc bất cứ điều gì - hoàn toàn tùy thuộc vào bạn. Chỉ là một séc bổ sung, ngoài ra còn được khởi chạy cứ sau 60 phút ....
marc_s

1
Giải pháp rất hay, đơn giản, ngắn gọn và sạch sẽ hoạt động tuyệt vời cho đến ngày hôm nay trong Windows 10 & .Net 4.7.2. !! Cảm ơn bạn.
MinimalTech

19

Tôi đã tạo một công cụ lập lịch đơn giản, dễ sử dụng và bạn không cần sử dụng thư viện bên ngoài. TaskScheduler là một singleton giữ các tham chiếu về bộ định thời để bộ định thời sẽ không bị thu thập rác, nó có thể lên lịch cho nhiều tác vụ. Bạn có thể đặt lần chạy đầu tiên (giờ và phút), nếu tại thời điểm lập lịch, thời gian này vượt quá thời gian lập lịch, hãy bắt đầu vào ngày hôm sau, thời điểm này tại thời điểm đó. Nhưng nó rất dễ dàng để tùy chỉnh mã.

Lên lịch cho một nhiệm vụ mới thật đơn giản. Ví dụ: Vào lúc 11:52, nhiệm vụ đầu tiên là cho mỗi 15 giây, ví dụ thứ hai là cho mỗi 5 giây. Để thực hiện hàng ngày, hãy đặt 24 thành tham số 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
    });

Cửa sổ gỡ lỗi của tôi:

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

Chỉ cần thêm lớp này vào dự án của bạn:

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

mới Timer không có phương thức nhận 4 đối số.
Fandango68

Tôi giả sử Timer ở ​​đây là từ System.Timers? Bạn có thể cung cấp một chương trình làm việc mẫu không? Cảm ơn
Fandango68

1
@ Fandango68 Tôi đã sử dụng Bộ hẹn giờ từ không gian tên System.Threading. Có một Hẹn giờ khác trong System.Timers. Tôi nghĩ rằng bạn đã sử dụng sai bằng cách sử dụng ở trên.
jannagy02

@ jannagy02 Làm cách nào để lên lịch nhiệm vụ cho một ngày cụ thể như Thứ Hai?
Sruthi Varghese


8

Như những người khác đã nói, bạn có thể sử dụng một ứng dụng bảng điều khiển để chạy khi được lên lịch. Những gì những người khác chưa nói là bạn có thể ứng dụng này kích hoạt một quy trình chéo EventWaitHandle mà bạn đang chờ đợi trong ứng dụng chính của mình.

Ứng dụng bảng điều khiển:

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

Ứng dụng chính:

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

Phương pháp tốt nhất mà tôi biết và có lẽ là đơn giản nhất là sử dụng Trình lập lịch tác vụ Windows để thực thi mã của bạn vào một thời điểm cụ thể trong ngày hoặc để ứng dụng của bạn chạy vĩnh viễn và kiểm tra một thời điểm cụ thể trong ngày hoặc viết một dịch vụ windows thực hiện tương tự.


4

Tôi biết điều này là cũ nhưng làm thế nào về điều này:

Xây dựng bộ hẹn giờ để kích hoạt khi khởi động để tính toán thời gian cho thời gian chạy tiếp theo. Ở cuộc gọi đầu tiên của thời gian chạy, hãy hủy bộ hẹn giờ đầu tiên và bắt đầu bộ hẹn giờ hàng ngày mới. thay đổi hàng ngày thành hàng giờ hoặc bất cứ điều gì bạn muốn chu kỳ.


9
Và xem nó không thành công khi thay đổi Giờ tiết kiệm ánh sáng ban ngày. . .
Jim Mischel

3

Đây là một cách để làm điều này bằng cách sử dụng TPL. Không cần tạo / hủy bỏ bộ hẹn giờ, v.v.:

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

Chương trình nhỏ này sẽ là giải pháp ;-)

Tôi hy vọng điều này sẽ giúp tất cả mọi người.

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

Nếu bạn muốn tệp thực thi chạy, hãy sử dụng Tác vụ theo lịch trình của Windows. Tôi sẽ giả định (có lẽ do nhầm lẫn) rằng bạn muốn một phương thức chạy trong chương trình hiện tại của mình.

Tại sao không chỉ có một luồng chạy liên tục lưu trữ ngày cuối cùng mà phương thức được gọi?

Để nó thức dậy mỗi phút (ví dụ) và nếu thời gian hiện tại lớn hơn thời gian được chỉ định và ngày cuối cùng được lưu trữ không phải là ngày hiện tại, hãy gọi phương thức rồi cập nhật ngày.


1

Có thể chỉ là tôi nhưng có vẻ như hầu hết các câu trả lời này không hoàn chỉnh hoặc không hoạt động chính xác. Tôi đã làm một cái gì đó rất nhanh và bẩn. Điều đó được cho là không chắc ý tưởng tốt như thế nào khi thực hiện theo cách này, nhưng nó hoạt động hoàn hảo mọi lúc.

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
Theo như tôi có thể thấy đây sẽ là một sự chờ đợi bận rộn và do đó không phải là một ý tưởng hay. vi.wikipedia.org/wiki/Busy_waiting
Tommy Ivarsson

1

Tôi vừa mới viết ứng dụng ac # phải khởi động lại hàng ngày. Tôi nhận ra câu hỏi này đã cũ nhưng tôi không nghĩ là sẽ đau khi thêm một giải pháp khả thi khác. Đây là cách tôi xử lý việc khởi động lại hàng ngày vào một thời điểm cụ thể.

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

Để đảm bảo bộ đếm thời gian của bạn được giữ trong phạm vi, tôi khuyên bạn nên tạo nó bên ngoài phương pháp bằng System.Timers.Timer tmrRestart = new System.Timers.Timer()phương pháp. Đặt phương thức RestartApp()trong sự kiện tải biểu mẫu của bạn. Khi ứng dụng khởi chạy, nó sẽ đặt các giá trị AppRestartnếu currentlớn hơn thời gian khởi động lại mà chúng tôi thêm 1 ngày AppRestartđể đảm bảo việc khởi động lại diễn ra đúng thời gian và chúng tôi không có ngoại lệ khi đặt giá trị âm vào bộ hẹn giờ. Trong trường tmrRestart_Elapsedhợp này, hãy chạy bất kỳ mã nào bạn cần chạy vào thời điểm cụ thể đó. Nếu ứng dụng của bạn tự khởi động lại, bạn không nhất thiết phải dừng bộ đếm thời gian nhưng nó cũng không ảnh hưởng gì, Nếu ứng dụng không khởi động lại, chỉ cần gọi lại RestartApp()phương thức và bạn sẽ sẵn sàng.


1

Tôi thấy điều này rất hữu ích:

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

Thay vì đặt thời gian để chạy mỗi giây trong mỗi 60 phút, bạn có thể tính thời gian còn lại và đặt bộ hẹn giờ thành một nửa (hoặc một số phần khác) của thời gian này. Bằng cách này, bạn không cần kiểm tra thời gian nhiều mà còn duy trì mức độ tích lũy khi khoảng thời gian hẹn giờ càng giảm bạn càng gần đến thời gian mục tiêu.

Ví dụ: nếu bạn muốn làm điều gì đó trong 60 phút kể từ bây giờ, khoảng thời gian của bộ hẹn giờ sẽ gần như tương đối:

30:00:00, 15:00:00, 07:30:00, 03:45:00, ..., 00:00:01, CHẠY!

Tôi sử dụng mã bên dưới để tự động khởi động lại dịch vụ mỗi ngày một lần. Tôi sử dụng một chủ đề vì tôi nhận thấy bộ hẹn giờ không đáng tin cậy trong thời gian dài, trong khi điều này tốn kém hơn trong ví dụ này, nó là bộ định thời duy nhất được tạo cho mục đích này nên điều này không quan trọng.

(Được chuyển đổi từ 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);
    }
}

Lưu ý Tôi đã đặt khoảng thời gian tối thiểu là 1000 mili giây, khoảng thời gian này có thể được tăng lên, giảm bớt hoặc loại bỏ tùy thuộc vào khả năng bạn yêu cầu.

Hãy nhớ cũng dừng chuỗi / bộ đếm thời gian của bạn khi ứng dụng của bạn đóng.


0

Tôi có một cách tiếp cận đơn giản cho điều này. Điều này tạo ra độ trễ 1 phút trước khi hành động xảy ra. Bạn cũng có thể thêm giây để tạo Thread.Sleep (); ngắn hơn.

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 giờ lần

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

Một ví dụ đơn giản cho một nhiệm vụ:

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

Giải pháp với 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);
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.