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


108

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

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

回答:


118

どちらSystem.Timers.TimerSystem.Threading.Timerサービスのために動作します。

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

System.Timers.Timer次の例のように使用します(また、ティム・ロビンソンの回答で述べられているように、ガベージコレクションを防ぐためにクラスレベル変数を使用していることを確認してください)。

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ページからのものです。


1
GC.KeepAlive(aTimer);、 aTimerはインスタンス変数です。たとえば、フォームのインスタンス変数の場合、フォームがある限り、常に参照されますね。
giorgim、2015年

37

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

これは一般的に行われているベストプラクティスです。 ジョン・ギャロウェイは私に同意します。または多分その逆です。 どちらの方法でも、タイマーから実行される断続的なタスクを実行するWindowsサービスを作成することはベストプラクティスではありません。

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

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


26
サービスが1日中実行されることを意図している場合、おそらく、スケジュールされたタスクではなくサービスが理にかなっています。または、サービスがあると、アプリケーションチームほど精通していないインフラストラクチャグループの管理とロギングが簡素化される可能性があります。ただし、サービスが必要であるという仮定に疑問を投げかけることは完全に有効であり、これらの否定的な評価は不当です。両方に+1。
sfuqua

2
@mrナンセンス。スケジュールされたタスクは、OSのコア部分です。FUDは自分で保管してください。

4
@MR:私は伝道者ではありません、私は現実主義者です。そして現実から、スケジュールされたタスクは「非常にバグが多い」わけではないことがわかりました。実際、それを支持すると主張したのはあなた次第です。そうでなければ、あなたがしていることは恐怖、不確実性、疑念を広げることだけです。

13
ここで泥沼に足を踏み入れるのは嫌いですが、MRを少し守る必要があります。私の会社では、Windowsコンソールアプリという重要なアプリケーションをいくつか実行しています。これらすべてを実行するためにWindowsタスクスケジューラを使用しました。現在、少なくとも5回、スケジューラサービスがなんらかの理由で「混乱」した問題がありました。タスクは実行されず、一部は奇妙な状態でした。唯一の解決策は、サーバーの再起動、またはスケジューラサービスの停止と開始でした。管理者権限がないとできることではなく、運用環境では受け入れられません。たったの0.02ドル。
SpaceCowboy74 2013年

6
これは実際には、いくつかのサーバー(これまでに3台)で発生しました。それが当たり前だとは言わない。時々言うだけで、何かを行う独自の方法を実装することは悪くありません。
SpaceCowboy74 2013年

7

どちらでも問題ありません。実際、System.Threading.TimerはSystem.Timers.Timerを内部的に使用します。

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

これまでに見た問題は何ですか?


Windows PhoneアプリがSystem.Threading.Timerにしかアクセスできないのはなぜですか。
Stonetip 2013年

Windows Phoneはおそらくフレームワークの軽量バージョンを備えており、両方のメソッドを使用できるようにするための追加のコードをすべて持つことはおそらく必要ないため、含まれていません。Nickの答えは、System.Timers.Timerスローされた例外を処理できないためにWindows Phoneがアクセスできない理由を説明するものだと思います。
マラキ2013

2

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

この意志:

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

3
しかし、これにはログオンしたユーザーが必要ですか?したがって、サーバー上で24時間年中無休で実行される場合は、サービスの方が優れている可能性があります。
JP Hellemons、2011

1

既に述べたように両方System.Threading.TimerSystem.Timers.Timer動作します。2つの大きな違いはSystem.Threading.Timer、もう1つのラッパーを囲むラッパーです。

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

これは過去に私に大きな問題を与えたので、私は常に「System.Threading.Timer」を使用し、それでもあなたの例外を非常にうまく処理しました。


0

私はこのスレッドが少し古いことを知っていますが、私が持っていた特定のシナリオに役立ちました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();
            }
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.