- PR -

ReleaseでビルドしたexeだとWindowsサービスが終了しない

投稿者投稿内容
でん
会議室デビュー日: 2005/02/14
投稿数: 5
投稿日時: 2005-05-17 16:01
こんにちは。

VS.NET 2003(C#)で作成したWindowsサービスで、Releaseモードでビルドしたexeだと、Windowsサービスへ停止指示をしても終了しないので困っております。

数秒毎にある処理を行うWindowsサービスを作成しています。
タイマーを使用して処理を行っており、もしタイマーイベント内での処理中にサービス停止指示が行われても、その処理が完全に終わるまではOnStopイベントを終了しないようにしております。(以下のサンプルソースを参照)

以下サンプルソースで
1.Debugモードでビルド
2.サービスを起動
3.10秒ちょっと経過した後にサービス停止
とすると、2のサービス起動から40秒後(タイマイベント発生まで10秒+タイマイベント内30秒)にはサービスが正常終了します。
しかし、
1.Releaseモードでビルド
2.サービスを起動
3.10秒ちょっと経過した後にサービス停止
とすると、CPU負荷が100%近くなり、しかも何秒経ってもサービスが終了しません。
いろいろ調べると、どうやらタイマイベントが終了しても、一度入ったOnStopイベントで何もせず、OnStopイベントの先頭で止めたはずのタイマが動作して再度タイマイベントが発生しているようです。

私の方法が悪いのか?それとも.NETがおかしいのか判りません。
どなたかお教え願います。

VSのバージョンは、7.1.3091です。
Frameworkのバージョンは、1.1.4322(SP1)です。
開発OS、実行OSともにXP(SP2)です。
サービスのインストールは、セットアッププロジェクトを作成して行っています。

<<サンプルソース>>

// タイマクラス変数
private System.Timers.Timer mobjTimer;

// タイマイベント実行中フラグ
private bool mbolOnTimerEvent;

protected override void OnStart(string[] args)
{
// タイマ起動
mobjTimer = new System.Timers.Timer();
mobjTimer.Elapsed += new System.Timers.ElapsedEventHandler(this.mobjTimer_Elapsed);
mobjTimer.Interval = 10000;
mobjTimer.AutoReset = true;
mobjTimer.Start();
mobjTimer.Enabled = true;
}

protected override void OnStop()
{
// タイマ停止
mobjTimer.Enabled = false;
mobjTimer.Stop();

while(true)
{
// タイマイベントが終了したらサービスを停止する
if( !mbolOnTimerEvent )
break;
}
}

private void mobjTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// タイマイベント無効
mobjTimer.Enabled = false;
// タイマイベント実行中フラグON
mbolOnTimerEvent = true;

// ダミー
System.Threading.Thread.Sleep(30000);

// タイマイベント有効
mobjTimer.Enabled = true;
// タイマイベント実行中フラグOFF
mbolOnTimerEvent = false;
}


[ メッセージ編集済み 編集者: でん 編集日時 2005-05-17 16:07 ]

[ メッセージ編集済み 編集者: でん 編集日時 2005-05-17 17:03 ]
nodera
大ベテラン
会議室デビュー日: 2003/09/08
投稿数: 200
投稿日時: 2005-05-17 17:10
こんにちは。

コード:
while(true) 
{ 
	// タイマイベントが終了したらサービスを停止する 
	if( !mbolOnTimerEvent ) 
		break; 
}



CPUが100%になってしまうのは、このコードが原因でしょう。フラグがfalseになるまで、このロジックはループしますが、ウェイトを置いていないので、CPUパワー全開でループすること間違いなし。

適当にスレッドスリープ入れてみてください。
コード:
while(true) 
{ 
	// タイマイベントが終了したらサービスを停止する 
	if( !mbolOnTimerEvent ) 
		break; 

	System.Threading.Thread.Sleep(300);
}



サービスってあまり作ったことがないんでよく分からんけど、タイマー起動する処理部分て別スレッド化したほうがよいのかな。。。
甕星
ぬし
会議室デビュー日: 2003/03/07
投稿数: 1185
お住まい・勤務地: 湖の見える丘の上
投稿日時: 2005-05-17 17:27
たぶん変数の宣言がvolatileなっていないのが原因。
正しくは下のように宣言する必要があるかと・・・。
private volatile bool mbolOnTimerEvent;

他のスレッドが値を変更する可能性がある場合、volatile宣言しないとコンパイル時の最適化によって意図しない動作になる事があります。例えば今回の場合だと、mbolOnTimerEvent変数はOnStop関数のWhileループ内では変更されていないので、メモリを直接参照せずにレジスタに割り当ててしまっても問題ない。メモリの値が変更されても、レジスタには反映されないので無限ループになる・・・とかね。
でん
会議室デビュー日: 2005/02/14
投稿数: 5
投稿日時: 2005-05-17 18:24
noderaさん、甕星さん、レスありがとうございます。

結果としましては、noderaさんの方法でループ内にSleepを1秒入れることで現象は起きなくなりました。
しかし、このスレッドを立てる前に、Sleepを試してダメだったはずなので、本当の原因がよく判らないのが現状です・・・。私がミスしたのかも知れませんが・・・。
タイマのインターバルやタイマイベント内の処理秒数等で現象が起きたり起きなかったりするのかも知れません。

あと私のほうでも調べていましたら、以下のようなことがTimerのStopイベントについてMSDNライブラリに書いてありましたので、載せておきます。私が最初に載せた、

> いろいろ調べると、どうやらタイマイベントが終了しても、一度入ったOnStopイベントで何もせず、OnStopイベントの先頭で止めたはずのタイマが動作して再度タイマイベントが発生しているようです。

と関係ありそうなことです。以下MSDNライブラリより

「あるスレッドで実行されている場合に、同時に別のスレッドでイベント処理メソッドの呼び出しが実行される可能性があることを示しています。(Stop メソッドを呼び出した後でも、 Elapsed イベントが発生することがあります。)この現象を回避するには、 SignalTime プロパティを使用して、イベントが発生した時刻と Stop メソッドを呼び出した時刻を比較します。」

ということで、様々な要因で起こっている気がしましたので、全てのことを考慮して以下のようなソースで行こうと思います。

// タイマクラス変数
private System.Timers.Timer mobjTimer;
// タイマイベント実行中フラグ
private volatile bool mbolOnTimerEvent;
// タイマストップ時刻
private volatile DateTime mdteTimerStop;

protected override void OnStart(string[] args)
{
// タイマ停止時刻初期化
mdteTimerStop = DateTime.Parse("9999/12/31 23:59:59");
// タイマ起動
mobjTimer = new System.Timers.Timer();
mobjTimer.Elapsed += new System.Timers.ElapsedEventHandler(this.mobjTimer_Elapsed);
mobjTimer.Interval = 10000;
mobjTimer.AutoReset = true;
mobjTimer.Start();
mobjTimer.Enabled = true;
}

protected override void OnStop()
{
// タイマ停止時刻取得
mdteTimerStop = DateTime.Now;
// タイマ停止
mobjTimer.Enabled = false;
mobjTimer.Stop();

while(true)
{
// タイマイベントが終了したらサービスを停止する
if( !mbolOnTimerEvent )
break;
Thread.Sleep(1000);
}
}

private void mobjTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// タイマストップされた時刻より後の場合は、イベントを実行しない
if (mdteTimerStop <= e.SignalTime)
return;

// タイマイベント無効
mobjTimer.Enabled = false;
// タイマイベント実行中フラグON
mbolOnTimerEvent = true;

Thread.Sleep(30000);

// タイマイベント有効
mobjTimer.Enabled = true;
// タイマイベント実行中フラグOFF
mbolOnTimerEvent = false;
}


[ メッセージ編集済み 編集者: でん 編集日時 2005-05-17 19:25 ]

[ メッセージ編集済み 編集者: でん 編集日時 2005-05-17 19:38 ]
ya
大ベテラン
会議室デビュー日: 2002/05/03
投稿数: 212
投稿日時: 2005-05-17 19:56
volatile云々よりもこのコード、まともに動くのか怪しいと思う。
this.flag = false;

if(this.flag) {...}
が同時に動く前提でかかれてますが、この二つのステートメントが分割不可能であるとは保証されていないはずですよ。同時実行される可能性がある場合、System.Threading.Monitor, System.Threading.Interlockedを使用する必要があります。C#ならlockステートメントでもOK。
でん
会議室デビュー日: 2005/02/14
投稿数: 5
投稿日時: 2005-05-17 20:34
yaさん、レスありがとうございます。

ある変数を複数のスレッドから更新または照会される可能性がある場合は、更新または照会される場所をそれぞれLock(this){}で囲んで同時実行しないようにする必要があるということでしょうか?
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2005-05-17 20:43
引用:

でんさんの書き込み (2005-05-17 20:34) より:
ある変数を複数のスレッドから更新または照会される可能性がある場合は、更新または照会される場所をそれぞれLock(this){}で囲んで同時実行しないようにする必要があるということでしょうか?


単純にそういう話ではないと思いますが…
まあ、「正しく動作」するように、慎重に検討・検証する必要はあります。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2005-05-17 20:54
漫画描こうよ、漫画。頭で考えるより、漫画にする方がよくわかるって。
# 漫画→図表
# この場合、UMLでいう状態遷移図(ステートチャート)や、
# シーケンス図
_________________

スキルアップ/キャリアアップ(JOB@IT)