前回はサービスプロジェクトの作成を行いました。
ここでは最低限の動作の仕組みについて確認し、プログラムを実装してみます。
サービスの作成方法についてはサービスプロジェクト作成をご確認ください。
サービスにおいてもアプリケーションの作成と同様、開始〜終了の流れがあります。
ただしサービスでは開始・終了に加えて、一時停止やセッションの変更などを通知まで管理することができます。
サービスとアプリケーションの主な比較について記述します。
機能 | サービス | アプリケーション |
---|---|---|
使用するクラス | ServiceBase クラス | Form クラス |
起動時 | ServiceBase.OnStart 関数 | Form.OnLoad イベント |
終了時 | ServiceBase.OnStop 関数 | Form.Closing イベント Form.Closed イベント |
一時停止 / 再開 | ServiceBase.OnPause 関数 ServiceBase.OnContinue 関数 | なし |
セッションの 変更 | ServiceBase.OnSessionChange 関数 | なし |
シャットダウン | ServiceBase.OnShutdown 関数 | なし |
電源ステータス変更 | ServiceBase.OnPowerEvent 関数 | なし |
本格的なシステムの場合は考慮する必要がありますが、試験であれば起動と終了のみ考慮するだけでOKです。
起動の仕組について説明した後、OnStart関数とOnStop関数について説明します。
Mainプログラム
サービスプログラムにおいてもMain関数があります。
ここではMain関数の処理について確認してみます。
Main関数処理
Main関数はアプリケーションと同様にProgram.csファイルに自動生成されるため、分かりやすいです。
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
}
- ServiceToRunというServiceBase配列のインスタンスを作成しています。
- この配列(ServiceToRun)の先頭要素をServiceBaseの派生クラス(Service1)で初期化しています。
- ServiceBase.Run関数よりServiceToRunを実行します。
これによりService1で作成したサービスが起動します。
マイクロソフト社のリファレンスでは、ServiceBase.Run関数は「Service Control Managerに登録することでService Control ManagerからStartコマンドが発行され、結果起動する」といった表記がありますのでRun関数で直接起動している訳ではないようです。
Windowsフォームアプリケーションも同様にProgram.csファイルのMain関数から始まるのですが、こちらは、
Application.Run(new Form1());
といった形でApplicationクラスのRun関数からForm1インスタンスを実行しています。
Windowsフォームアプリケーションはこれでプロジェクト作成直後からフォーム画面を表示することができます。
Main関数の変更
Main関数の処理はこのままでも使えますが、複数のサービスを管理する前提?で配列定義されています。
多くの場合は1つのサービスの開発を行なうため、次のように変更してみます。
private static ServiceBase _servicesToRun;
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
static void Main()
{
_servicesToRun = new Service1();
ServiceBase.Run(_servicesToRun);
}
- _serviceToRunというメンバ変数に変更します。配列にはしません。
- 後は_serviceToRunをService1クラスで初期化し、Run関数で開始します。
これで少しですが簡略化できました。
また、_serviceToRunをメンバ変更にすることでサービス起動中もこのサービス制御を行なうことができます。
こちらについてはOnStop関数にて説明します。
OnStart関数
Run関数によりサービスが開始されるとOnStart関数が呼び出されます。
この関数はプロジェクト作成時のサービスクラス(ここではService1)にてオーバーライドされています。
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
}
protected override void OnStop()
{
}
}
OnStart関数の中に初期化したい処理を記載します。
ここではログファイルを作成するインスタンスを初期化してみます。
/// <summary>
/// ログ書き込み用
/// </summary>
private StreamWriter _log = null;
/// <summary>
/// OnStart関数
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
// 初期化
_log = new StreamWriter("D:log.txt");
// OnStartログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStartの呼び出し"));
_log.Flush();
}
- StreamWriterクラスのメンバ変数(_log)を作成します。nullで初期化しておきます。
- OnStart関数の先頭で_log変数を初期化します。ファイル名:log.txt
- 次にOnStartが呼び出されたことをログファイルに記録します。
サービスを実行し、ログに以下の呼び出し記録が行われます。
OnStop関数
サービスの停止時にはOnStop関数が呼び出されます。
こちらもOnStart関数と同様にプロジェクト作成時にオーバーライドされています。
同様にログを追加してみます。
/// <summary>
/// OnStop関数
/// </summary>
protected override void OnStop()
{
// OnStopログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStopの呼び出し"));
_log.Flush();
// ファイルを閉じてリソース解放
_log.Close();
_log.Dispose();
}
- 開始と同様、OnStopの呼び出しログを記載します。
- OnStop関数の末尾にて_logインスタンスを解放します。
サービスの開始と停止後ログにそれぞれ記録されます。
サービスの処理実装
開始と終了について説明しました。
ここからはサービス処理をどのように記載するか説明します。
OnStart関数内に実装
試しにOnStart関数でループさせ、サービスの処理を行なうプログラムを作成してみます。
NG例のため実行しないでください。
/// <summary>
/// OnStart関数
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
// 初期化
_log = new StreamWriter("D:log.txt");
// OnStartログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStartの呼び出し"));
_log.Flush();
while(true)
{
// ここにサービスで行いたい処理を入れる
// 負荷高くなるためSleep
Thread.Sleep(500);
}
}
- OnStartの末尾でループさせ、その中で行いたい処理を実装します。(ここでは未実装)
- 500ms間隔で処理を行なうためにSleep関数を追加します。
このプログラムを実行すると一見動作しているように見えますが、タスクマネージャーを確認するとサービスが「開始中」のまま進んでおりません。
これはOnStart関数がサービス起動の初期化を行なう関数であり、この関数が完了しないとサービスは「実行中」になりません。
このようにサービスで行いたい処理はOnStart関数が完了後に処理する必要があります。
タイマーによる実装
サービスの処理を実装する方法はいくつかあります。
ここでは簡単に作成できるタイマーを使ってみます。
最初にタイマーを定義します。
/// <summary>
/// サービス処理を行うタイマー
/// </summary>
private System.Timers.Timer _timer;
/// <summary>
/// OnStart関数
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
// 初期化
_log = new StreamWriter("D:log.txt");
// OnStartログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStartの呼び出し"));
_log.Flush();
// タイマーを初期化
_timer = new System.Timers.Timer();
_timer.Elapsed += OnTimedEvent;
_timer.Interval = 1000;
_timer.AutoReset = false;
_timer.Start();
}
- Timerクラスのメンバ変数(_timer)を作成します。
- タイマーの処理を行なう関数(OnTimedEvent)を作成します。
- OnStartの末尾でタイマーを初期化から開始まで行います。
ここでは初期化後にIntervalを1000msに設定し、1秒間隔で処理を行います。
AutoResetはタイマー処理に時間がかかる場合、処理中に再度呼び出しが発生するため1度だけ実行(false)にします。繰り返しはタイマーの最後で命令します。
次にタイマーの処理を行なう関数(OnTimedEvent)を実装します。
/// <summary>
/// タイマー処理
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
// OnTimedEventログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnTimedEventの呼び出し"));
_log.Flush();
// タイマー開始
((System.Timers.Timer)source).Start();
}
- OnTimedEvent関数が呼び出しされる度に、ログを出力します。
本来はここにサービスで行いたい処理を実装します。 - OnTimedEventの末尾でStart関数を呼び、タイマーを再開します。
これでサービス起動後に繰り返し処理が行えるようになりました。
サービスの終了
サービスを終了するにはタスクマネージャーなどの外部から「サービスの停止」を行うのが一般的ですが、自身でも終了したい場合があります。
Windowsフォームアプリケーションではメインのフォームをclose()する処理に相当します。
終了するにはServiceBase.Stop関数を使用します。
今回はOnTimedEvent関数を30回実行後、自動終了してみます。
まずはProgram.cs側に停止関数を追加します。
/// <summary>
/// サービス終了
/// </summary>
public static void ServiceStop()
{
_servicesToRun.Stop();
}
- ServiceStop関数を作成します。
- 関数の中でメンバ変数(_serviceToRun)のStop関数を実行します。
この関数は呼び出しされると、OnStop関数が呼び出されサービスが終了します。
次にOnTimedEvent関数を実装します。
/// <summary>
/// 終了までのカウント
/// </summary>
private int _count = 30;
/// <summary>
/// タイマー処理
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
// OnTimedEventログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnTimedEventの呼び出し"));
_log.Flush();
if ((--_count) > 0)
{
// タイマー開始
((System.Timers.Timer)source).Start();
}
else
{
// サービス終了
Program.ServiceStop();
}
}
- _count変数を初期値30で宣言します。
- OnTimedEvent関数実行時に_countをデクリメントし、0になったらServiceStop関数を呼び出しサービスを終了します。
30回実行後、サービスが自動停止しました。
以下、ソースコードです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace service_project
{
internal static class Program
{
private static ServiceBase _servicesToRun;
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
static void Main()
{
_servicesToRun = new Service1();
ServiceBase.Run(_servicesToRun);
}
/// <summary>
/// サービス終了
/// </summary>
public static void ServiceStop()
{
_servicesToRun.Stop();
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
namespace service_project
{
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
/// <summary>
/// ログ書き込み用
/// </summary>
private StreamWriter _log = null;
/// <summary>
/// サービス処理を行うタイマー
/// </summary>
private System.Timers.Timer _timer;
/// <summary>
/// OnStart関数
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
// 初期化
_log = new StreamWriter("D:log.txt");
// OnStartログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStartの呼び出し"));
_log.Flush();
// タイマーを初期化
_timer = new System.Timers.Timer();
_timer.Elapsed += OnTimedEvent;
_timer.Interval = 1000;
_timer.AutoReset = false;
_timer.Start();
}
/// <summary>
/// OnStop関数
/// </summary>
protected override void OnStop()
{
// OnStopログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnStopの呼び出し"));
_log.Flush();
// ファイルを閉じてリソース解放
_log.Close();
_log.Dispose();
}
/// <summary>
/// 終了までのカウント
/// </summary>
private int _count = 30;
/// <summary>
/// タイマー処理
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
// OnTimedEventログ書き込み
_log.WriteLine(string.Format("{0}_{1}", DateTime.Now.ToString(), "OnTimedEventの呼び出し"));
_log.Flush();
if ((--_count) > 0)
{
// タイマー開始
((System.Timers.Timer)source).Start();
}
else
{
// サービス終了
Program.ServiceStop();
}
}
}
}
まとめ
プロジェクト作成と開始・終了まで動かすことができました。
処理にオリジナルの実装を行なうことができますので、アイデア出てきましたらソフト作成についても紹介したいと思います。