VS 2012/.NET 4.5で実際にWindowsサービスを開発しています。
サービスは、以下のコードスニペットのスキームに従っています。
私が心配しているのは、誰かが管理コンソールを介してサービスを停止した場合、それはサービスが実行しているプロセスの間だけかもしれないということです。
リクエストを停止してWindowsサービスを停止する方法についていくつか読みましたが、少し迷っています。 WorkerThreadsが作成されることもあれば、ManualResetEventsが作成されることもありますが、今までのところ、Windowsサービスの最善の方法を十分に理解できていません。
Windowsサービスを停止する前に、onStopメソッドで処理が適切に終了するまで待つ必要があります。
次のコードスニペットも考慮して、次に進む最善の方法は何ですか?
皆さんありがとう!
namespace ImportationCV
{
public partial class ImportationCV : ServiceBase
{
private System.Timers.Timer _oTimer;
public ImportationCV()
{
InitializeComponent();
if (!EventLog.SourceExists(DAL.Utilities.Constants.LOG_JOURNAL))
{
EventLog.CreateEventSource(DAL.Utilities.Constants.LOG_JOURNAL, DAL.Utilities.Constants.SOURCE_JOURNAL);
}
EventLog.Source = DAL.Utilities.Constants.SOURCE_JOURNAL;
EventLog.Log = DAL.Utilities.Constants.LOG_JOURNAL;
}
protected override void OnStart(string[] args)
{
int intDelai = Properties.Settings.Default.WatchDelay * 1000;
_oTimer = new System.Timers.Timer(intDelai);
_oTimer.Elapsed += new ElapsedEventHandler(this.Execute);
_oTimer.Start();
EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " started at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
}
protected override void OnStop()
{
if (_oTimer != null && _oTimer.Enabled)
{
_oTimer.Stop();
_oTimer.Dispose();
}
EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " stopped at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
}
private void Execute(object source, ElapsedEventArgs e)
{
_oTimer.Stop();
try
{
//Process
}
catch (Exception ex)
{
EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, (ex.StackTrace + ("\r\n" + ex.Message)), EventLogEntryType.Error);
}
_oTimer.Start();
}
}
}
テストケースとして、WindowsサービスのSystem.Threading.Thread.Sleep(500000)
コールバックにOnStop()
への呼び出しを配置しました。サービスを開始してから停止しました。サービスコントロールマネージャー(SCM)がサービスを停止しようとしていることを示す進行状況バーが表示されたウィンドウが表示されました。約2分後、SCMから次の応答が返されました。
このウィンドウを閉じた後、SCMのサービスのステータスがStoppingに変わり、サービスがタスクマネージャーで実行され続けていることに気付きました。スリープ状態が経過した後(約6分後)、プロセスは停止しました。 SCMウィンドウを更新すると、サービスが実行されていないことが示されました。
これからいくつかのことを取り上げます。まず、OnStop()
は、システムでNiceをプレイする一環として、サービスをタイムリーに停止しようとする必要があります。次に、OnStop()
メソッドの構造に応じて、プリエンプティブリクエストを無視するようサービスに強制することができます。これはお勧めできませんが、あなたができたようです。
特定の状況に関して、あなたが理解しなければならないことは、_System.Timers.Timer.Elapsed
_イベント ThreadPool
スレッドで発生する であることです。定義により、これは バックグラウンドスレッド です。つまり、アプリケーションの実行を維持しません。サービスがシャットダウンするように指示されると、システムはすべてのバックグラウンドスレッドを停止してからプロセスを終了します。したがって、SCMからシャットダウンするように指示されても処理が完了するまで処理を続行することに対する懸念(== --- ==)は、現在構造化されている方法では発生しません。これを行うには、正式な_System.Threading.Thread
_オブジェクトを作成し、それをフォアグラウンドスレッドとして設定してから、タイマーを使用してこのスレッドをトリガーして実行します(Elapsed
コールバックでは行われません)。
そうは言っても、システムをうまく操作したいと思うので、要求されたときにサービスをタイムリーにシャットダウンします。たとえば、マシンを再起動する必要がある場合はどうなりますか?まだテストしていませんが、処理が完了するまでサービスの実行を強制すると、システムは実際にプロセスが完了するまで待ってから実際に再起動する場合があります。それは私が私のサービスに望んでいることではありません。
だから私は二つのうちの一つを提案するでしょう。最初のオプションは、処理を個別に実行できる個別のチャンクに分割することです。各チャンクが終了したら、サービスが停止しているかどうかを確認します。その場合は、スレッドを正常に終了します。これができない場合は、トランザクションのようなものを処理に導入します。一連のデータベーステーブルとやり取りする必要があり、データベースが不良な状態のままになっている可能性があるため、開始後にフローを中断すると問題が発生するとします。データベースシステムでトランザクションが許可されている場合、これは比較的簡単になります。そうでない場合は、メモリ内で可能なすべての処理を行い、最後の1秒間に変更をコミットします。この方法では、期間全体をブロックするのではなく、変更がコミットされている間のみシャットダウンをブロックします。それに価値があるので、シャットダウンコマンドをスレッドに伝えるためにManualResetEvent
を使用することを好みます。
これ以上混乱しないように、ここでは省略します。 HTH。
これは袖口から外れているので、その正確さは確認しません。あなた(または他の人)が見つけた問題を修正します。
2つのManualResetEvent
オブジェクトを定義します。1つはシャットダウン通知用、もう1つは処理通知用で、Thread
オブジェクトです。 OnStart()
コールバックを次のように変更します。
_using System.Threading;
using Timer = System.Timers.Timer; // both Threading and Timers have a timer class
ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
ManualResetEvent _processEvent = new ManualResetEvent(false);
Thread _thread;
Timer _oTimer;
protected override void OnStart(string[] args)
{
// Create the formal, foreground thread.
_thread = new Thread(Execute);
_thread.IsBackground = false; // set to foreground thread
_thread.Start();
// Start the timer. Notice the lambda expression for setting the
// process event when the timer elapses.
int intDelai = Properties.Settings.Default.WatchDelay * 1000;
_oTimer = new Timer(intDelai);
_oTimer.AutoReset = false;
_oTimer.Elapsed += (sender, e) => _processEvent.Set();
_oTimer.Start();
}
_
Execute()
コールバックを次のように変更します。
_private void Execute()
{
var handles = new WaitHandle[] { _shutdownEvent, _processEvent };
while (true)
{
switch (WaitHandle.WaitAny(handles))
{
case 0: // Shutdown Event
return; // end the thread
case 1: // Process Event
Process();
_processEvent.Reset(); // reset for next time
_oTimer.Start(); // trigger timer again
break;
}
}
}
_
次のようにProcess()
メソッドを作成します。
_private void Process()
{
try
{
// Do your processing here. If this takes a long time, you might
// want to periodically check the shutdown event to see if you need
// exit early.
}
catch (Exception ex)
{
// Do your logging here...
// You *could * also shutdown the thread here, but this will not
// stop the service.
_shutdownEvent.Set();
}
}
_
最後に、OnStop()
コールバックで、スレッドのシャットダウンをトリガーします。
_protected override void OnStop()
{
_oTimer.Stop(); // no harm in calling it
_oTimer.Dispose();
_shutdownEvent.Set(); // trigger the thread to stop
_thread.Join(); // wait for thread to stop
}
_
@Matt-素晴らしいコードをありがとう、本当に役に立ちました。 _shutdownEventに別のテストを追加すると、さらにうまくいくことがわかりました。
case 1: // Process Event
Process();
if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
...