Hẹn giờ tốt nhất để sử dụng trong dịch vụ Windows


108

Tôi cần tạo một số dịch vụ cửa sổ sẽ thực thi mỗi N khoảng thời gian.
Câu hỏi là:
Tôi nên sử dụng điều khiển hẹn giờ nào: System.Timers.Timerhay System.Threading.Timermột? Nó có ảnh hưởng đến điều gì không?

Tôi đang hỏi vì tôi đã nghe thấy nhiều bằng chứng cho thấy công việc không chính xác của các System.Timers.Timerdịch vụ trong windows.
Cảm ơn bạn.

Câu trả lời:


118

Cả hai System.Timers.TimerSystem.Threading.Timersẽ hoạt động cho các dịch vụ.

Các bộ hẹn giờ bạn muốn tránh là System.Web.UI.TimerSystem.Windows.Forms.Timer, tương ứng cho các ứng dụng ASP và WinForms. Việc sử dụng những thứ đó sẽ khiến dịch vụ tải một lắp ráp bổ sung không thực sự cần thiết cho loại ứng dụng bạn đang xây dựng.

Sử dụng System.Timers.Timergiống như ví dụ sau (ngoài ra, hãy đảm bảo rằng bạn sử dụng biến cấp lớp để ngăn chặn việc thu gom rác, như đã nêu trong câu trả lời của Tim Robinson):

using System;
using System.Timers;

public class Timer1
{
    private static System.Timers.Timer aTimer;

    public static void Main()
    {
        // Normally, the timer is declared at the class level,
        // so that it stays in scope as long as it is needed.
        // If the timer is declared in a long-running method,  
        // KeepAlive must be used to prevent the JIT compiler 
        // from allowing aggressive garbage collection to occur 
        // before the method ends. (See end of method.)
        //System.Timers.Timer aTimer;

        // Create a timer with a ten second interval.
        aTimer = new System.Timers.Timer(10000);

        // Hook up the Elapsed event for the timer.
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

        // Set the Interval to 2 seconds (2000 milliseconds).
        aTimer.Interval = 2000;
        aTimer.Enabled = true;

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

        // If the timer is declared in a long-running method, use
        // KeepAlive to prevent garbage collection from occurring
        // before the method ends.
        //GC.KeepAlive(aTimer);
    }

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

/* This code example produces output similar to the following:

Press the Enter key to exit the program.
The Elapsed event was raised at 5/20/2007 8:42:27 PM
The Elapsed event was raised at 5/20/2007 8:42:29 PM
The Elapsed event was raised at 5/20/2007 8:42:31 PM
...
 */

Nếu bạn chọn System.Threading.Timer, bạn có thể sử dụng như sau:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        AutoResetEvent autoEvent     = new AutoResetEvent(false);
        StatusChecker  statusChecker = new StatusChecker(10);

        // Create the delegate that invokes methods for the timer.
        TimerCallback timerDelegate = 
            new TimerCallback(statusChecker.CheckStatus);

        // Create a timer that signals the delegate to invoke 
        // CheckStatus after one second, and every 1/4 second 
        // thereafter.
        Console.WriteLine("{0} Creating timer.\n", 
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = 
                new Timer(timerDelegate, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every 
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period.\n");

        // When autoEvent signals the second time, dispose of 
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    int invokeCount, maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}

Cả hai ví dụ đều đến từ các trang MSDN.


1
Tại sao bạn lại đề xuất: GC.KeepAlive (aTimer) ;, aTimer là biến instance đúng, vì vậy ví dụ nếu nó là biến instance của form, sẽ luôn có tham chiếu đến nó miễn là có form, phải không?
giorgim

37

Không sử dụng dịch vụ cho việc này. Tạo một ứng dụng bình thường và tạo một tác vụ đã lên lịch để chạy nó.

Đây là phương pháp hay nhất thường được áp dụng. Jon Galloway đồng ý với tôi. Hoặc có thể là theo cách khác. Dù bằng cách nào, thực tế là không phải là phương pháp hay nhất để tạo một dịch vụ windows để thực hiện một tác vụ ngắt quãng khi chạy bộ hẹn giờ.

"Nếu bạn đang viết một Dịch vụ Windows chạy bộ hẹn giờ, bạn nên đánh giá lại giải pháp của mình."

–Jon Galloway, quản lý chương trình cộng đồng ASP.NET MVC, tác giả, siêu anh hùng bán thời gian


26
Nếu dịch vụ của bạn dự định chạy cả ngày, thì có lẽ một dịch vụ có ý nghĩa hơn là một nhiệm vụ theo lịch trình. Hoặc, có một dịch vụ có thể đơn giản hóa việc quản trị và ghi nhật ký cho một nhóm cơ sở hạ tầng không hiểu biết như nhóm ứng dụng. Tuy nhiên, đặt câu hỏi về giả định rằng một dịch vụ được yêu cầu là hoàn toàn hợp lệ và những xếp hạng tiêu cực này không được phục vụ. +1 cho cả hai.
sfuqua

2
@mr Vớ vẩn. Các tác vụ đã lên lịch là một phần cốt lõi của HĐH. Vui lòng giữ FUD của bạn cho riêng mình.

4
@MR: Tôi không phải là một nhà truyền giáo, tôi là một người thực tế. Và thực tế đã dạy tôi rằng các nhiệm vụ được lên lịch không phải là "quá lỗi". Trên thực tế, nó phụ thuộc vào bạn với tư cách là người đưa ra tuyên bố ủng hộ nó. Nếu không, tất cả những gì bạn đang làm là gieo rắc nỗi sợ hãi, sự không chắc chắn và nghi ngờ.

13
Tôi ghét phải lội vào bãi lầy ở đây, nhưng tôi phải bảo vệ MR một chút. Tôi có một số ứng dụng quan trọng đang chạy trong công ty của mình, đó là các ứng dụng bảng điều khiển windows. Tôi đã sử dụng Windows Task Scheduler để chạy tất cả chúng. Trong ít nhất 5 lần bây giờ, chúng tôi đã gặp sự cố trong đó Dịch vụ lập lịch biểu "bị nhầm lẫn" bằng cách nào đó. Các nhiệm vụ không được thực hiện và một số ở trạng thái kỳ lạ. Giải pháp duy nhất là khởi động lại máy chủ hoặc dừng và khởi động dịch vụ Bộ lập lịch. Không phải điều gì tôi có thể làm nếu không có quyền quản trị và không được chấp nhận trong môi trường sản xuất. Chỉ 0,02 đô la của tôi.
SpaceCowboy74

6
Nó thực sự đã xảy ra với tôi trên một vài máy chủ (3 cho đến nay). Mặc dù vậy, sẽ không nói đó là tiêu chuẩn. Chỉ cần nói rằng đôi khi thực hiện phương pháp của riêng bạn để làm điều gì đó không phải là xấu.
SpaceCowboy74

7

Một trong hai sẽ hoạt động tốt. Trên thực tế, System.Threading.Timer sử dụng System.Timers.Timer trong nội bộ.

Phải nói rằng, thật dễ dàng để sử dụng sai System.Timers.Timer. Nếu bạn không lưu trữ đối tượng Timer trong một biến ở đâu đó, thì nó có thể bị thu gom. Nếu điều đó xảy ra, bộ hẹn giờ của bạn sẽ không hoạt động nữa. Gọi phương thức Dispose để dừng bộ đếm thời gian hoặc sử dụng lớp System.Threading.Timer, là một trình bao bọc đẹp hơn một chút.

Bạn đã thấy những vấn đề gì cho đến nay?


Khiến tôi thắc mắc tại sao một ứng dụng Windows Phone chỉ có thể truy cập System.Threading.Timer.
Stonetip

Windows phone có thể có phiên bản nhẹ hơn của framework, có tất cả mã bổ sung đó để có thể sử dụng cả hai phương pháp có lẽ không cần thiết nên nó không được bao gồm. Tôi nghĩ rằng câu trả lời của Nick, đưa ra một lý do tốt hơn cho lý do tại sao điện thoại Windows không có quyền truy cập System.Timers.Timerbởi vì nó sẽ không xử lý các Ngoại lệ được đưa ra.
Malachi

2

Tôi đồng ý với nhận xét trước đó là tốt nhất có thể xem xét một cách tiếp cận khác. Đề xuất của tôi sẽ là viết một ứng dụng bảng điều khiển và sử dụng bộ lập lịch cửa sổ:

Điều này sẽ:

  • Giảm mã đường ống dẫn nước sao chép hành vi của bộ lập lịch
  • Cung cấp tính linh hoạt hơn về hành vi lập lịch (ví dụ: chỉ chạy vào cuối tuần) với tất cả logic lập lịch được trừu tượng hóa từ mã ứng dụng
  • Sử dụng các đối số dòng lệnh cho các tham số mà không cần phải thiết lập các giá trị cấu hình trong các tệp cấu hình, v.v.
  • Dễ dàng hơn để gỡ lỗi / kiểm tra trong quá trình phát triển
  • Cho phép người dùng hỗ trợ thực thi bằng cách gọi trực tiếp ứng dụng bảng điều khiển (ví dụ: hữu ích trong các tình huống hỗ trợ)

3
Nhưng điều này yêu cầu một người dùng đã đăng nhập? Vì vậy, dịch vụ có thể tốt hơn nếu nó chạy 24/7 trên một máy chủ.
JP Hellemons 19/12/11

1

Như đã nêu cả hai System.Threading.TimerSystem.Timers.Timersẽ hoạt động. Sự khác biệt lớn giữa cả hai System.Threading.Timerlà một trình bao bọc bao quanh cái còn lại.

System.Threading.Timersẽ có nhiều xử lý ngoại lệ hơn trong khi System.Timers.Timersẽ nuốt tất cả các ngoại lệ.

Điều này đã gây ra cho tôi những vấn đề lớn trong quá khứ vì vậy tôi sẽ luôn sử dụng 'System.Threading.Timer' và vẫn xử lý rất tốt các ngoại lệ của bạn.


0

Tôi biết chủ đề này hơi cũ nhưng nó có ích cho một tình huống cụ thể mà tôi đã có và tôi nghĩ nó đáng để lưu ý rằng có một lý do khác tại sao System.Threading.Timercó thể là một cách tiếp cận tốt. Khi bạn phải thực hiện định kỳ một Công việc có thể mất nhiều thời gian và bạn muốn đảm bảo rằng toàn bộ khoảng thời gian chờ được sử dụng giữa các công việc hoặc nếu bạn không muốn công việc chạy lại trước khi công việc trước đó kết thúc trong trường hợp công việc mất nhiều thời gian hơn khoảng thời gian hẹn giờ. Bạn có thể sử dụng như sau:

using System;
using System.ServiceProcess;
using System.Threading;

    public partial class TimerExampleService : ServiceBase
    {
        private AutoResetEvent AutoEventInstance { get; set; }
        private StatusChecker StatusCheckerInstance { get; set; }
        private Timer StateTimer { get; set; }
        public int TimerInterval { get; set; }

        public CaseIndexingService()
        {
            InitializeComponent();
            TimerInterval = 300000;
        }

        protected override void OnStart(string[] args)
        {
            AutoEventInstance = new AutoResetEvent(false);
            StatusCheckerInstance = new StatusChecker();

            // Create the delegate that invokes methods for the timer.
            TimerCallback timerDelegate =
                new TimerCallback(StatusCheckerInstance.CheckStatus);

            // Create a timer that signals the delegate to invoke 
            // 1.CheckStatus immediately, 
            // 2.Wait until the job is finished,
            // 3.then wait 5 minutes before executing again. 
            // 4.Repeat from point 2.
            Console.WriteLine("{0} Creating timer.\n",
                DateTime.Now.ToString("h:mm:ss.fff"));
            //Start Immediately but don't run again.
            StateTimer = new Timer(timerDelegate, AutoEventInstance, 0, Timeout.Infinite);
            while (StateTimer != null)
            {
                //Wait until the job is done
                AutoEventInstance.WaitOne();
                //Wait for 5 minutes before starting the job again.
                StateTimer.Change(TimerInterval, Timeout.Infinite);
            }
            //If the Job somehow takes longer than 5 minutes to complete then it wont matter because we will always wait another 5 minutes before running again.
        }

        protected override void OnStop()
        {
            StateTimer.Dispose();
        }
    }

    class StatusChecker
        {

            public StatusChecker()
            {
            }

            // This method is called by the timer delegate.
            public void CheckStatus(Object stateInfo)
            {
                AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
                Console.WriteLine("{0} Start Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                //This job takes time to run. For example purposes, I put a delay in here.
                int milliseconds = 5000;
                Thread.Sleep(milliseconds);
                //Job is now done running and the timer can now be reset to wait for the next interval
                Console.WriteLine("{0} Done Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                autoEvent.Set();
            }
        }
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.