Bir Windows hizmetinde kullanmak için en iyi Zamanlayıcı


108

Her N zaman aralığında çalışacak bazı Windows hizmeti oluşturmam gerekiyor.
Soru şudur:
Hangi zamanlayıcı kontrolünü kullanmalıyım: System.Timers.Timerveya System.Threading.Timerbiri? Bir şeyi etkiliyor mu?

Soruyorum, çünkü System.Timers.TimerWindows hizmetlerinde yanlış çalıştığına dair birçok kanıt duydum .
Teşekkür ederim.

Yanıtlar:


118

Hem System.Timers.Timerve System.Threading.Timerhizmetler için çalışacaktır.

Kaçınmak istediğiniz zamanlayıcılar System.Web.UI.Timerve System.Windows.Forms.Timerbunlar sırasıyla ASP uygulamaları ve WinForms içindir. Bunları kullanmak, hizmetin, inşa ettiğiniz uygulama türü için gerçekten gerekli olmayan ek bir montaj yüklemesine neden olacaktır.

System.Timers.TimerAşağıdaki örnekte olduğu gibi kullanın (ayrıca, Tim Robinson'un cevabında belirtildiği gibi, çöp toplamayı önlemek için sınıf düzeyinde bir değişken kullandığınızdan emin olun):

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

İsterseniz System.Threading.Timeraşağıdaki gibi kullanabilirsiniz:

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

Her iki örnek de MSDN sayfalarından gelir.


1
Neden öneriyorsunuz: GC.KeepAlive (aTimer) ;, aTimer doğru örnek değişkendir, bu nedenle örneğin formun örnek değişkeni ise, form olduğu sürece her zaman ona referans olacaktır, değil mi?
giorgim

37

Bunun için bir hizmet kullanmayın. Normal bir uygulama oluşturun ve onu çalıştırmak için zamanlanmış bir görev oluşturun.

Bu, yaygın olarak kabul edilen en iyi uygulamadır. Jon Galloway benimle aynı fikirde. Ya da belki tam tersi. Her iki durumda da gerçek şu ki, bir zamanlayıcı üzerinden aralıklı bir görevi gerçekleştirmek için bir Windows hizmeti oluşturmak en iyi uygulamalar değildir.

"Zamanlayıcı çalıştıran bir Windows Hizmeti yazıyorsanız, çözümünüzü yeniden değerlendirmelisiniz."

–Jon Galloway, ASP.NET MVC topluluk program yöneticisi, yazar, yarı zamanlı süper kahraman


26
Hizmetinizin tüm gün çalışması amaçlanıyorsa, belki bir hizmet zamanlanmış bir görevden daha mantıklı olabilir. Veya bir hizmete sahip olmak, uygulama ekibi kadar anlayışlı olmayan bir altyapı grubu için yöneticiyi ve günlük kaydını basitleştirebilir. Ancak, bir hizmetin gerekli olduğu varsayımının sorgulanması tamamen geçerlidir ve bu olumsuz derecelendirmeler hak edilmemektedir. Her ikisine de +1.
sfuqua

2
@mr Saçma. Zamanlanmış görevler, işletim sisteminin temel bir parçasıdır. Lütfen FUD'nuzu kendinize saklayın.

4
@MR: Ben bir misyoner değilim, ben bir realistim. Ve gerçeklik bana zamanlanmış görevlerin "aşırı derecede hatalı" olmadığını öğretti. Aslında, onu desteklediğini iddia eden kişi olarak size kalmış. Aksi takdirde, yaptığınız tek şey korku, belirsizlik ve şüphe yaymaktır.

13
Burada bataklığa girmekten nefret ediyorum ama MR'ı biraz savunmam gerekiyor. Şirketimde Windows konsolu uygulamaları olan birkaç kritik uygulama çalışıyorum. Hepsini çalıştırmak için Windows Görev Zamanlayıcı'yı kullandım. Şu anda en az 5 kez, Zamanlayıcı Hizmetinin bir şekilde "kafasının karıştığı" bir sorun yaşadık. Görevler yürütülmedi ve bazıları garip bir durumdaydı. Tek çözüm, sunucunun yeniden başlatılması veya Zamanlayıcı hizmetinin durdurulması ve başlatılmasıydı. Yönetici hakları olmadan yapabileceğim bir şey değil ve bir üretim ortamında kabul edilemez. Sadece 0,02 dolarım.
SpaceCowboy74

6
Aslında birkaç sunucuda başıma geldi (şimdiye kadar 3). Yine de bunun norm olduğunu söylemeyeceğim. Sadece bazen bir şeyi yapmak için kendi yönteminizi uygulamanın kötü olmadığını söylüyorum.
SpaceCowboy74

7

İkisi de iyi çalışmalı. Aslında, System.Threading.Timer dahili olarak System.Timers.Timer kullanır.

Bunu söyledikten sonra, System.Timers.Timer'ı kötüye kullanmak kolaydır. Timer nesnesini bir yerde bir değişkende saklamazsanız, o zaman çöp toplanmaya yatkındır. Bu olursa, zamanlayıcınız artık çalışmayacaktır. Zamanlayıcıyı durdurmak için Dispose yöntemini çağırın veya biraz daha hoş bir sarmalayıcı olan System.Threading.Timer sınıfını kullanın.

Şimdiye kadar ne tür sorunlar gördünüz?


Bir Windows Phone uygulamasının neden yalnızca System.Threading.Timer'a erişebildiğini merak etmeme neden oluyor.
Stonetip

Windows telefonları muhtemelen çerçevenin daha hafif bir sürümüne sahiptir, her iki yöntemi de kullanabilmek için tüm bu ekstra koda sahip olmak muhtemelen gerekli değildir, bu nedenle dahil değildir. Sanırım Nick'in cevabı, System.Timers.Timerkendisine atılan İstisnaları işlemeyeceği için Windows telefonlarının neden erişemediğine dair daha iyi bir neden veriyor .
Malachi

2

Farklı bir yaklaşımı düşünmenin en iyisi olabilecek önceki yoruma katılıyorum. Benim önerim bir konsol uygulaması yazmak ve Windows planlayıcısını kullanmak olacaktır:

Bu irade:

  • Planlayıcı davranışını kopyalayan tesisat kodunu azaltın
  • Uygulama kodundan soyutlanmış tüm programlama mantığıyla zamanlama davranışı açısından daha fazla esneklik sağlayın (örneğin yalnızca hafta sonları çalışır)
  • Yapılandırma dosyalarında vb. Yapılandırma değerlerini ayarlamak zorunda kalmadan parametreler için komut satırı bağımsız değişkenlerini kullanın.
  • Geliştirme sırasında hata ayıklamak / test etmek çok daha kolay
  • Bir destek kullanıcısının doğrudan konsol uygulamasını çalıştırarak yürütmesine izin verin (örneğin, destek durumlarında kullanışlıdır)

3
Ancak bu oturum açmış bir kullanıcı gerektirir? Bu nedenle, bir sunucuda 7/24 çalışacaksa hizmet belki daha iyidir.
JP Hellemons

1

Daha önce belirtildiği hem System.Threading.Timerve System.Timers.Timerçalışacaktır. İkisi arasındaki en büyük fark System.Threading.Timer, diğerinin etrafında bir sarmalayıcı olmasıdır.

System.Threading.TimerSystem.Timers.Timertüm istisnaları yutarken daha fazla istisna işleme olacaktır.

Bu bana geçmişte büyük sorunlar verdi, bu yüzden her zaman 'System.Threading.Timer' kullanıyordum ve yine de istisnalarınızı çok iyi idare ediyordum.


0

Bu iş parçacığının biraz eski olduğunu biliyorum, ancak sahip olduğum belirli bir senaryo için işe yaradı ve System.Threading.Timeriyi bir yaklaşım olmasının başka bir nedeni olduğunu not etmeye değer olduğunu düşündüm . Uzun zaman alabilecek bir İşi periyodik olarak yürütmeniz gerektiğinde ve bekleme süresinin tamamının işler arasında kullanıldığından emin olmak istediğinizde veya işin önceki iş bitmeden önce tekrar çalışmasını istemiyorsanız iş zamanlayıcı süresinden daha uzun sürüyor. Aşağıdakileri kullanabilirsiniz:

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();
            }
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.