web-dev-qa-db-ja.com

Windowsサービスで使用するのに最適なタイマー

N期間ごとに実行されるWindowsサービスを作成する必要があります。
質問は:
使用するタイマーコントロールはSystem.Timers.TimerまたはSystem.Threading.Timerのどれですか。それは何かに影響しますか?

WindowsサービスでのSystem.Timers.Timerの正しくない動作に対する多くの証拠を聞いたので、私は尋ねています。
ありがとうございました。

108
nKognito

System.Timers.TimerSystem.Threading.Timer の両方がサービスに対して機能します。

回避したいタイマーは System.Web.UI.TimerSystem.Windows.Forms.Timer で、それぞれASPアプリケーションとWinForms用です。これらを使用すると、サービスは追加のアセンブリをロードしますが、これは構築中のアプリケーションのタイプには実際には必要ありません。

次の例のように System.Timers.Timer を使用します(また、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
...
 */

System.Threading.Timer を選択すると、次のように使用できます。

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

どちらの例もMSDNページからのものです。

118
Andrew Moore

これにはサービスを使用しないでください。通常のアプリケーションを作成し、それを実行するスケジュールされたタスクを作成します。

これは、一般的に行われているベストプラクティスです。 Jon Gallowayは同意します。あるいは、その逆かもしれません。 どちらの場合でも、実際には、タイマーから実行される断続的なタスクを実行するWindowsサービスを作成することはベストプラクティスではありません。

"タイマーを実行するWindowsサービスを作成している場合は、ソリューションを再評価する必要があります。"

–Jon Galloway、ASP.NET MVCコミュニティプログラムマネージャー、著者、パートタイムスーパーヒーロー

37
Will

どちらも正常に動作するはずです。実際、System.Threading.TimerはSystem.Timers.Timerを内部的に使用します。

そうは言っても、System.Timers.Timerを誤用するのは簡単です。 Timerオブジェクトをどこかの変数に保存しないと、ガベージコレクションが行われやすくなります。その場合、タイマーは作動しなくなります。 Disposeメソッドを呼び出してタイマーを停止するか、System.Threading.Timerクラスを使用します。これは、若干優れたラッパーです。

これまでにどのような問題が発生しましたか?

7
Tim Robinson

別のアプローチを検討するのが最善かもしれない以前のコメントに同意します。私の提案は、コンソールアプリケーションを作成し、Windowsスケジューラを使用することです。

この意志:

  • スケジューラの動作を再現する配管コードを削減します
  • アプリケーションコードから抽象化されたすべてのスケジューリングロジックを使用して、スケジューリング動作に関する柔軟性を高めます(週末のみの実行など)。
  • 構成ファイルなどで構成値を設定することなく、パラメーターのコマンドライン引数を使用します。
  • 開発中のデバッグ/テストがはるかに簡単
  • コンソールアプリケーションを直接呼び出して、サポートユーザーが実行できるようにします(たとえば、サポート状況で役立ちます)
2
user32378

既に述べたように、System.Threading.TimerSystem.Timers.Timerの両方が機能します。 2つの大きな違いは、System.Threading.Timerが他のラッパーの周りのラッパーであることです。

System.Threading.Timerはより多くの例外処理を行い、System.Timers.Timerはすべての例外を飲み込みます。

これにより、過去に大きな問題が発生したため、常に「System.Threading.Timer」を使用し、例外を非常にうまく処理していました。

1
Nick N.

私はこのスレッドが少し古いことを知っていますが、特定のシナリオに役立ちました。System.Threading.Timerが良いアプローチになる別の理由があることに注意する価値があると思いました。時間がかかる可能性のあるジョブを定期的に実行する必要があり、ジョブ間で待機期間全体を使用したい場合、または前のジョブが終了する前にジョブを再度実行したくない場合ジョブはタイマー期間よりも長くかかります。次を使用できます。

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