[2019/04/05] サービスプログラムの作り方について・その4(メイン処理内で別のスレッドを呼出す) (No.90)
[2018/10/26] サービスプログラムの作り方について・その3(メイン処理をスレッド化) (No.62)
[2018/10/26] サービスプログラムの作り方について・その2 (No.61)
[2018/10/25] サービスプログラムの作り方について・その1 (No.60)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
前回「その3」では「メイン処理スレッドの実際の中身を組み込んでいきたいと思います」と記しましたが、実際のプログラム例を示します。
今回の例ではメイン処理スレッド内で、時刻が毎分00秒になった場合、サブ処理のスレッドを起動します。
毎分00秒の判定方法は、メインループ内で毎回、現在時刻の秒を取得し、初期状態から"00"になった時にスレッド開始のトリガとしています。
スレッド開始時の秒を前回値として退避しておきます。 この前回値が設定済みで、現在時刻の秒が"00"ではない場合に、再度前回値を初期状態に戻しています。
この一連の処理はループ最後の待ち時間が1秒より十分小さくないと成り立ちません。
(尚、このトリガ時刻判定はもっといい方法があるかもしれませんが。)
サブ処理のスレッドの中身は、単に1秒毎に現在時刻をイベントログの書き出す処理を3回行っています。 3回の処理を終わるとこのスレッドは処理を終え、消滅します。
実際のソースは以下の通りです。■サービスPG・メイン処理内で別のスレッドを呼出す
Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります Me.EventLog.WriteEntry("サービスを開始します。") 'メイン処理スレッドの生成を行う Me.mThreadMain = New Threading.Thread(New Threading.ThreadStart(AddressOf Me.MainProc)) Me.mThreadMain.Start() End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 Me.EventLog.WriteEntry("サービスを停止します。") 'メインスレッド停止依頼フラグON Me.mblnThreadStop = True 'メインスレッドの停止を待つ Me.mThreadMain.Join() End Sub ' スレッドクラス Private mThreadMain As Threading.Thread ' スレッド停止フラグ Private mblnThreadStop As Boolean = False ' メイン処理スレッド Private Sub MainProc() Me.EventLog.WriteEntry("MainProc...開始。") Try '前回秒の初期状態 Dim strLastSS As String = "" 'メインスレッドのループ While mblnThreadStop = False '現在の秒の取得 Dim strSS As String = Now.ToString("ss") If strLastSS = "" Then '前回秒の初期状態の場合 If strSS = "00" Then '時刻が毎分00秒の場合、サブスレッド開始 Dim t As New Threading.Thread(New Threading.ThreadStart(AddressOf SubProc)) t.Start() '現在の秒の退避 strLastSS = strSS End If Else If strSS <> "00" Then '前回秒の初期化 strLastSS = "" End If End If ' 約0.1秒待ち時間を入れる(このプロセスがCPUを独占しない様に) Threading.Thread.Sleep(100) End While Catch ex As Exception ' エラー処理があればここに書く Me.EventLog.WriteEntry("MainProc...エラー" & ex.Message) End Try Me.EventLog.WriteEntry("MainProc...停止。") End Sub 'サブ処理スレッド Private Sub SubProc() Me.EventLog.WriteEntry("SubProc...開始。") Try 'ループを3回で処理終了 For i As Integer = 1 To 3 '時刻の時間.分.秒.ミリ秒の表示 Me.EventLog.WriteEntry("SubProc..." & Now.ToString("HH:mm:ss.fff")) ' 約1秒待ち時間を入れる Threading.Thread.Sleep(1000) Next Catch ex As Exception ' エラー処理があればここに書く Me.EventLog.WriteEntry("SubProc...エラー" & ex.Message) End Try Me.EventLog.WriteEntry("SubProc...停止。") End Sub End Class
このサービスを実際に開始し、ログの結果を「その3」同様 Windowsの powershell を起動し Get-EventLog コマンド でログの一覧を見てみます。
「Windows + R」 で「ファイル名を指定して実行」を開き、 powershell を起動します。
powershell 起動後、以下のコマンドをキー入力してログ一覧を行います。- Get-EventLog Application -Newest 26
ログを見ますと、ほぼ毎分00秒でサブスレッドが起動され、1秒毎に3回時刻が登録されています。この例ではサービスを起動後、約4分後にサービスを停止させています。
サービスの処理では外部からのリクエストにより何かの処理を行い結果を返すというのが一般的な処理ではないかと思われます。 今回の例ではタイマをリクエストのトリガとして使っています。タイマもサービスプログラムから見れば外部と考えられなくもありませんが。
外部からのリクエストとしては、通信による方法だとか、原始的な方法ではファイルによるものだとかいろいろ考えられます。 今後は外部からのリクエストに応答するサービスを例に挙げていきたいと思います。
さて、今回の例でメイン処理のなかで、サブスレッドを起動していますが、 外部から複数の起動が掛かることを将来的に考えるのであれば、 サブスレッドを複数起動しそれぞれのスレッドのログが判別できる様にしてみます。
サブスレッドの処理をクラスでラップし、クラスの中に内部のスレッドの起動メソッドを設けてみます。■サービスPG・メイン処理内で別のスレッドを呼出す(別スレッドをクラス化)
Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります Me.EventLog.WriteEntry("サービスを開始します。") 'メイン処理スレッドの生成を行う Me.mThreadMain = New Threading.Thread(New Threading.ThreadStart(AddressOf Me.MainProc)) Me.mThreadMain.Start() End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 Me.EventLog.WriteEntry("サービスを停止します。") 'メインスレッド停止依頼フラグON Me.mblnThreadStop = True 'メインスレッドの停止を待つ Me.mThreadMain.Join() End Sub ' スレッドクラス Private mThreadMain As Threading.Thread ' スレッド停止フラグ Private mblnThreadStop As Boolean = False ' メイン処理スレッド Private Sub MainProc() Me.EventLog.WriteEntry("MainProc...開始。") Try '前回秒の初期状態 Dim strLastSS As String = "" 'メインスレッドのループ While mblnThreadStop = False '現在の秒の取得 Dim strSS As String = Now.ToString("ss") If strLastSS = "" Then '前回秒の初期状態の場合 If strSS = "00" Then '時刻が毎分00秒の場合、3個のサブスレッド開始 For i As Integer = 1 To 3 'スレッド名前 Dim strName As String = "Test" & i.ToString 'スレッドクラス生成 Dim clsSubTread As New SubThread(strName, Me.EventLog) 'クラス内のスレッド開始 clsSubTread.Start() Next '現在の秒の退避 strLastSS = strSS End If Else If strSS <> "00" Then '前回秒の初期化 strLastSS = "" End If End If ' 約0.1秒待ち時間を入れる(このプロセスがCPUを独占しない様に) Threading.Thread.Sleep(100) End While Catch ex As Exception ' エラー処理があればここに書く Me.EventLog.WriteEntry("MainProc...エラー" & ex.Message) End Try Me.EventLog.WriteEntry("MainProc...停止。") End Sub '===== 'サブスレッド用ラップクラス '===== Class SubThread '名前 Private mstrName As String 'イベントログ Private mEventLog As System.Diagnostics.EventLog 'コンストラクタ Sub New(strName As String, EventLog As System.Diagnostics.EventLog) Me.mstrName = strName Me.mEventLog = EventLog End Sub '内部スレッドの開始 Sub Start() Dim t As New Threading.Thread(New Threading.ThreadStart(AddressOf SubProc)) t.Start() End Sub 'サブ処理スレッド Private Sub SubProc() Me.mEventLog.WriteEntry("SubProc[" & Me.mstrName & "]...開始。") Try 'ループを3回で処理終了 For i As Integer = 1 To 3 '時刻の時間.分.秒.ミリ秒の表示 Me.mEventLog.WriteEntry("SubProc[" & Me.mstrName & "]..." & Now.ToString("HH:mm:ss.fff")) ' 約1秒待ち時間を入れる Threading.Thread.Sleep(1000) Next Catch ex As Exception ' エラー処理があればここに書く Me.mEventLog.WriteEntry("SubProc[" & Me.mstrName & "]...エラー" & ex.Message) End Try Me.mEventLog.WriteEntry("SubProc[" & Me.mstrName & "]...停止。") End Sub End Class End Class
ログを見ますと、ほぼ毎分00秒で3個のサブスレッドが起動され、1秒毎に3回時刻が登録されています。
3個のスレッドは実行が順番通りではありません。これはスレッドを同時に起動したとしても、スレッドの優先がどれになるかは分からない様です。
サブスレッドをクラス化しましたが、各スレッドで少し違った処理をさせたい場合にはクラス化してその中で場合分けした方が組みやすいと思います。関連する記事
⇒サービスプログラムの作り方について・その1
⇒サービスプログラムの作り方について・その2
⇒サービスプログラムの作り方について・その3(メイン処理をスレッド化)
おすすめ書籍
PR -
前回「その2」では「メイン処理をスレッドにしたもので行います」と記しましたが、実際のプログラム例を示します。
プログラムの手順としては以下の様になります。- OnStart ではイベントログに開始メッセージを登録し、メイン処理スレッドの生成を行う。
- OnStop ではイベントログに停止メッセージを登録し、メインスレッド停止依頼フラグONし、メイン処理スレッドの生成を行う。
- メイン処理スレッドでは停止依頼フラグがONになるまでループ処理を行う。ループ内では Sleep で待ちを入れる。
■サービスPG・メイン処理をスレッド化
Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります Me.EventLog.WriteEntry("サービスを開始します。") 'メイン処理スレッドの生成を行う Me.mThreadMain = New Threading.Thread(New Threading.ThreadStart(AddressOf Me.MainProc)) Me.mThreadMain.Start() End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 Me.EventLog.WriteEntry("サービスを停止します。") 'メインスレッド停止依頼フラグON Me.mblnThreadStop = True 'メインスレッドの停止を待つ Me.mThreadMain.Join() End Sub ' スレッドクラス Private mThreadMain As Threading.Thread ' スレッド停止フラグ Private mblnThreadStop As Boolean = False ' メイン処理スレッド Private Sub MainProc() Me.EventLog.WriteEntry("MainProc...開始。") Try While mblnThreadStop = False ' ここに本来のサービス処理を書く ' 待ち時間を入れる(このプロセスがCPUを独占しない様に) Threading.Thread.Sleep(100) End While Catch ex As Exception ' エラー処理があればここに書く End Try Me.EventLog.WriteEntry("MainProc...停止。") End Sub End Class
このプログラムの重要な点は、サービスの「開始」が行われた時に、 メイン処理をスレッドとして起動を掛けて即座に「開始」処理から抜け出るところです。 こうするのは「開始」処理の中では時間の掛かる処理が実行できないからです。
また、メインスレッド内でも Sleep 関数での待ちを入れていますが、 これが無いとこのスレッドがCPU時間を独占することになりますので、必要になります。
しかし、まだこれではサービスプログラムの枠を作っただけで、実際に何も処理は起こりません。
サービスプログラムが何を行うのかが、決まっていないからですが、 上記のソース内の「ここに本来のサービス処理を書く」の部分に書いていくことになります。
さて、今回のサービスプログラムを実際に「開始」し、「停止」した場合のイベントログを見てみます。
「コントロールパネル」⇒「管理ツール」⇒「イベント ビューアー」で起動し、 実際にイベントログが登録されているかを見てみます。確かにログは登録されている様ですが、上から6件のログを一覧で見てみます。
ログの一覧を表示するには、Windowsの powershell を起動し Get-EventLog コマンドを使います。
それでは 「Windows + R」 で「ファイル名を指定して実行」を開き、 powershell を起動します。
(尚 Get-EventLog の詳しい説明はここでは行いません)
powershell 起動後、以下のコマンドをキー入力してログ一覧を行います。- Get-EventLog Application -Newest 6
ログを見ますと、期待した通りの結果が得られました。
次の記事ではメイン処理スレッドの実際の中身を組み込んでいきたいと思います。関連する記事
⇒サービスプログラムの作り方について・その1
⇒サービスプログラムの作り方について・その2
⇒サービスプログラムの作り方について・その4(メイン処理内で別のスレッドを呼出す)
おすすめ書籍
-
前回「その1」ではサービスの処理として何もしなかったのですが、今回は少しだけ処理を追加していきます。
タイマコントロールを追加し、タイマイベント処理の中で、Windowsのイベントログに時刻の出力を行います。
TestService.vb[デザイン] のフォームを開き、タイマコントロールを設置するのですが、 「ツールボックス」にはタイマコントロールが現れていないと思いますので、 「ツールボックス」のウインドウの上でマウス右クリックして「アイテムの選択」を実行します。 さらに「.NET Framework コンポーネント」の名前が Timer で、名前空間が System.Timers をチェックし「OK」を押下します。
System.Timers.Timer を TestService.vb[デザイン] へドラッグ&ドロップし、タイマコントロールを設置します。 Timer1 のプロパティで以下の設定にします。- Enabled:False
- Interval:10000 (10秒毎のタイマイベントの発生)
尚、ServiceBase の TestService のプロパティ「AutoLog」が「True」になっているのを確認して下さい。 デフォルトで「True」なのですが、Microsoftのサイトによりますと、以下の様に書かれています。既定では、すべての Windows サービス プロジェクトはアプリケーション イベント ログとやり取りして、 そこに情報および例外を書き込むことができます。
アプリケーションにこの機能が必要かどうかを指定するには、 AutoLog プロパティを使用します。
既定では、Windows サービス プロジェクト テンプレートで作成したサービスには、ログが有効にされます。
EventLog クラスの静的フォームを使用すると、 EventLog コンポーネントのインスタンスを作成したり、手動でソースを登録したりすることなく、ログにサービス情報を書き込むことができます。では今回のソースとしては以下の様になります。
OnStart ではイベントログに開始メッセージを登録し、タイマの開始を指示します。
OnStop ではイベントログに停止メッセージを登録し、タイマの停止を指示します。
タイマイベントの Timer1_Elapsed では現在時刻をイベントログに登録します。Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります Me.EventLog.WriteEntry("サービスを開始します。") ' タイマの開始 Me.Timer1.Enabled = True Me.EventLog.WriteEntry("サービスを開始しました。") End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 Me.EventLog.WriteEntry("サービスを停止します。") ' タイマの停止 Me.Timer1.Enabled = False Me.EventLog.WriteEntry("サービスを停止しました。") End Sub ' タイマイベント処理 Private Sub Timer1_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles Timer1.Elapsed Try 'この段階では特に行う処理が無いのでイベントログへの出力を行う Me.EventLog.WriteEntry("タイマイベント:" & Now.ToString("yyyy/MM/dd HH:mm:ss")) Catch ex As Exception End Try End Sub End Class
特にどうといった処理ではないので簡単な処理です。
このサービスプログラムですが、何かイベントが起こるのをこのプログラムの裏?で待っているわけで、 今回はタイマコントロールによるイベントを受け付ける様になっています。
「コントロールパネル」⇒「管理ツール」⇒「イベント ビューアー」で起動し、 実際にイベントログが登録されているかを見てみます。イベントログに「タイマイベント:YYYY/MM/DD HH:mm:ss」が正しく登録されている様です。
尚、「AutoLog」が「True」の場合には、 OnStart OnStop などではシステムがイベントログにそれぞれのメッセージを登録するため、 本当は上記の「サービスを開始しました。」「サービスを停止しました。」の登録は必要ありません。
ところで、OnStart OnStop の処理は長時間実行可能なわけでは無く、すぐに終わらせる必要があります。 その為、OnStart の中ではタイマコントロールをEnable にして、すぐに処理を終わっています。
実際のところタイマコントロールでは面白くないので、別スレッドを起動して、スレッド側でメイン処理として行う方がいいと思います。 次の記事ではメイン処理をスレッドにしたもので行います。関連する記事
⇒サービスプログラムの作り方について・その1
⇒サービスプログラムの作り方について・その3(メイン処理をスレッド化)
⇒サービスプログラムの作り方について・その4(メイン処理内で別のスレッドを呼出す)
おすすめ書籍
-
サービスプログラムの作成を行うことがあったので、作成方法の最初の部分を記します。
サービスプログラムはフォームを持つプログラムとは全く性格が異なり、作成手順も異なりますので、 手順を追いながら作成していきます。 尚、今回の作業を行うには、Windowsの管理者権限でログインしておいてください。
先ず、VisualStudioを起動し、新しいプロジェクトの作成を行います。 「新しいプロジェクト」ウインドウが表示されたら、テンプレートの中のVisualBasicを選択し、更に Windowsサービス を選択すると以下の様な表示なります。
「名前」が「WindowsServices1」となっているのを、仮に「TestService」に変更し、「OK」ボタンを押下します。
尚、画面はプロジェクト名を「TestService」と変更し、更に「TestService.vb[デザイン]」のプロパティで、「ServiceName」も「TestService」に全て変更しました。
ここをクリックするとコードビューに切り替わります。のリンクをクリックします。 すると、Service1.vb の内容が以下の様に表示されます。
ソースとしては以下の様になります。
Public Class TestService Protected Overrides Sub OnStart(ByVal args() As String) ' サービスを開始するコードをここに追加します。このメソッドによって、 ' サービスが正しく実行されるようになります End Sub Protected Overrides Sub OnStop() ' サービスを停止するのに必要な終了処理を実行するコードをここに追加します。 End Sub End Class
OnStart プロシージャと OnStop プロシージャのサービスへの機能追加を行い、再定義する他のすべてのメソッドを上書きします。
OnStart プロシージャは、「コントロールパネル」⇒「管理ツール」⇒「サービス」で起動される、画面の中で、各サービスでの「開始」操作に対応する処理を記述します。 また、OnStop プロシージャは、各サービスでの「停止」操作に対応する処理を記述します。
最初のテストなので、このままの状態でサービスを作成し、インストール行います。
「TestService.vb[デザイン]」を表示し、デザイン上で右クリックでメニューを表示させて、「インストーラーの追加」をクリックします.インストーラーのコントロールがプロジェクトに追加されて、以下の様な表示なります.
[ProjectInstaller] の [デザイン] ビューで、[ServiceInstaller1] を選択します。
[プロパティ] ウィンドウで、ServiceName プロパティが TestService に設定されていることを確認します。
「ソリューション エクスプローラー」の「ソリューション'TestService'」を選択し、VisualSutudioのメニューの「ビルド」⇒「ソリューションのビルド」を選択し、ビルドを行います.尚、ここでひとつ忘れていたのですが、「TestService」プロパティ設定で、スタートアップオブジェクトを「TestService」に設定しておきます.
では、作成されたサービスを実際に登録してみます。 登録方法は、Windows の [スタート] メニューまたは [スタート] 画面で、[Visual Studio]⇒[Visual Studio ツール]⇒[開発者コマンド プロンプト] の順に選択します。
Visual Studio コマンド プロンプトが表示されますので、インストールする場合は以下のコマンドを入力します。- installutil C:\TestService\TestService\bin\Debug\TestService.exe
アンインストールする場合は以下のコマンドを入力します。
- installutil /u C:\TestService\TestService\bin\Debug\TestService.exe
インストールコマンドを実行すると以下の様に、ログイン時のユーザー名とパスワードを聞いてきますので、 それぞれを入力して作業を進めます。
尚、ユーザー名の注意ですが、ログインしている私のマシンのAdmin属性のユーザを設定したのですが、ユーザ名の先頭に「.\」(ドット¥)が必要でした。インストールが完了すれば以下の表示になります。
これでサービスに登録が終わりましたので、「コントロールパネル」⇒「管理ツール」⇒「サービス」を起動します。 サービスの一覧の中に「TestService」があると思いますので、サービスの「開始」を行います。以下の様に、サービスの状態が「開始」になります。
サービスを止めるには「停止」を指定します。
アンインストールを実際に行った様子を以下に示します。結果、サービス一覧から「TestService」が削除されました。
今回はサービスプログラムの生成と登録までを行っただけで、実際に何かの処理を行うわけではありません。
今後は、何かの処理を行う部分を組み込んで、サービスプログラムについて考察していきたいと思います。関連する記事
⇒サービスプログラムの作り方について・その2
⇒サービスプログラムの作り方について・その3(メイン処理をスレッド化)
⇒サービスプログラムの作り方について・その4(メイン処理内で別のスレッドを呼出す)
おすすめ書籍