本文由网友投稿。
作者:傲慢与偏见
原文标题:使用 C#简单制作一个看门狗程序
原文链接:https://www.cnblogs.com/chonglu/p/16913746.html
まずは読者の皆様のご支援に感謝いたします:

技術系の記事の投稿を歓迎します。テーマは問いませんが、稿料はありません…

要約
特殊なプロジェクトでは、ソフトウェアが無人で動作することがあります。プログラムが理由なくクラッシュしたり、プロセスが強制終了されたりした場合に備えて、ウォッチドッグプログラムを開発することは非常に重要です。それはまるで不死身の小さな虫のようなもので、プログラムが異常終了した場合、即座に監視対象のプログラムを再起動します。
コード実装
Tips:記事の最後に完全なソースコードがありますので、ステップバイステップの説明は省略します。
- Dog クラスを作成します。主に一定間隔で監視対象プログラムが動作しているか確認します。
タイマーを設定し、5秒ごとにチェックします。プロセスが見つからなければProcessを使ってプログラムを起動します。
public class Dog
{
private Timer timer = new Timer();
private string processName ;
private string filePath;//監視対象プログラムのパス
public Dog()
{
timer.Interval = 5000;
timer.Tick += timer_Tick;
}
public void Start(string filePath)
{
this.filePath = filePath;
this.processName = Path.GetFileNameWithoutExtension(filePath);
timer.Enabled = true;
}
/// <summary>
/// 定期的にシステムが動作しているか確認
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timer_Tick(object sender, EventArgs e)
{
try
{
Process[] myproc = Process.GetProcessesByName(processName);
if (myproc.Length == 0)
{
Log.Info("監視対象プログラムが終了したことを検出しました。プログラムを再起動します。プログラムパス:{0}",filePath);
ProcessStartInfo info = new ProcessStartInfo
{
WorkingDirectory = Path.GetDirectoryName(filePath),
FileName = filePath,
UseShellExecute = true
};
Process.Start(info);
Log.Info("監視対象プログラムを起動しました");
}
}
catch (Exception)
{
}
}
}
- プログラムのエントリポイントで監視対象プログラムのパスを受け取り、
Dogのスキャンを開始します。
static class Program
{
static NotifyIcon icon = new NotifyIcon();
private static Dog dog = new Dog();
/// <summary>
/// アプリケーションのメインエントリポイント。
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args == null || args.Length == 0)
{
MessageBox.Show("起動パラメータが異常です", "ヒント", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string filePath = args[0];
if(!File.Exists(filePath))
{
MessageBox.Show("起動パラメータが異常です", "ヒント", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
Process current = Process.GetCurrentProcess();
Process[] processes = Process.GetProcessesByName(current.ProcessName);
//現在のプロセスと同じ名前のプロセスリストを走査
foreach (Process process in processes)
{
//既にインスタンスが存在する場合は現在のプロセスを無視
if (process.Id != current.Id)
{
//起動しようとしているプロセスが既存のプロセスと同じファイルパスであることを確認
if (process.MainModule.FileName.Equals(current.MainModule.FileName))
{
//既存のプロセスがある場合
return;
}
else
{
process.Kill();
process.WaitForExit(3000);
}
}
}
icon.Text = "ウォッチドッグ";
icon.Visible = true;
Log.Info("ウォッチドッグを起動します。監視対象プログラム:{0}",filePath);
dog.Start(filePath);
Application.Run();
}
}
- シンプルなログ記録クラスを実装します(サードパーティライブラリを使用しても構いませんが、監視プログラムは依存関係がない方が望ましいです)。下記は、外部依存がなく非常にシンプルなものです。
public class Log
{
//読み書きロック。リソースが書き込みモードのとき、他のスレッドは書き込みが終了するまで待機
private static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
//ログファイルパス
public static string logPath = "logs\\dog.txt";
//静的コンストラクタ:最初のアクセス前に自動的に呼び出され、ログファイルの存在を確認
static Log()
{
//フォルダを作成
if (!Directory.Exists("logs"))
{
Directory.CreateDirectory("logs");
}
}
/// <summary>
/// ログを書き込む。
/// </summary>
public static void Info(string format, params object[] args)
{
try
{
LogWriteLock.EnterWriteLock();
string msg = args.Length > 0 ? string.Format(format, args) : format;
using (FileStream stream = new FileStream(logPath, FileMode.Append))
{
StreamWriter write = new StreamWriter(stream);
string content = String.Format("{0} {1}",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),msg);
write.WriteLine(content);
//ストリームを閉じてリソースを解放
write.Close();
write.Dispose();
}
}
catch (Exception e)
{
}
finally
{
LogWriteLock.ExitWriteLock();
}
}
}
これで監視プログラムは完成です。次に、メインプログラム(監視対象プログラム)に起動・停止用のクラスを実装します。
- メインプログラムにウォッチドッグの起動・停止クラスを実装
public static class WatchDog
{
private static string processName = "WatchDog"; //監視プログラムのプロセス名(注意:監視対象プログラムではありません。もし間違えるとどうなるか試してみてください)
private static string appPath = AppDomain.CurrentDomain.BaseDirectory; //アプリケーションの起動ディレクトリ
/// <summary>
/// ウォッチドッグを起動
/// </summary>
public static void Start()
{
try
{
string program = string.Format("{0}{1}.exe", appPath, processName);
ProcessStartInfo info = new ProcessStartInfo
{
WorkingDirectory = appPath,
FileName = program,
CreateNoWindow = true,
UseShellExecute = true,
Arguments = Process.GetCurrentProcess().MainModule.FileName //監視対象プログラムの完全パス
};
Process.Start(info);
}
catch (Exception)
{
}
}
/// <summary>
/// ウォッチドッグを停止
/// </summary>
public static void Stop()
{
Process[] myproc = Process.GetProcessesByName(processName);
foreach (Process pro in myproc)
{
pro.Kill();
pro.WaitForExit(3000);
}
}
}
原理は単純で、2点注意が必要です:
processNameフィールドは監視プログラムを示します。監視対象プログラムではありません。逆にすると…(試してみてください)。Argumentsパラメータは監視対象プログラムの完全パスです。通常、監視対象プログラムから監視プログラムを起動するため、Process.GetCurrentProcess().MainModule.FileNameで監視対象プログラムの完全パスを取得できます。
- メインプログラムのエントリポイントでウォッチドッグを起動
public partial class App : Application
{
[STAThread]
static void Main()
{
//プログラム起動前に監視プログラムを呼び出す
WatchDog.Start();
Application app = new Application();
MainWindow mainWindow = new MainWindow();
app.Run(mainWindow);
}
}
Winform、通常のWPF、Prismなどでエントリポイントは異なりますので、プロジェクトの実情に合わせて柔軟に対応してください。
最後に、プログラムを正常終了させたい場所(メインプログラムの閉じるボタンなど)でウォッチドッグプログラムを停止します。
効果
