はじめに
本記事では、Semaphoreを利用して多重起動を制御する方法の備忘録です。
このような処理を実施する必要がある例として下記の様なことを想定してみます。
あるアプリケーションを自動で起動させるシステムがあったとして、そのアプリケーションがある理由により終了しないケースに陥った場合、どんどんアプリケーションのプロセスが増えていってしまうというケース
上記の場合に陥らない様に、Semaphoreを利用した排他制御を行うための方法をまとめました。
Semaphoreとは
Semaphore(セマフォ)とは、同時にアクセスできるスレッド数を制限するする方法です。
簡単にまとめると「現在の実行数をカウント」でき、「同時に実行できる数を指定」できます。
詳しくは、下記を参考にしてください。
実際に利用してみる
今回は多重起動を禁止するような処理としています。
(同時エントリ数:1)
また、これはすでに前回のプロセスが終了していない状態を考慮しています。
string smpName = "SampleName";
if(Semaphore.TryOpenExisting(smpName, out Semaphore smp) == true)
{
smp.WaitOne();
}
else
{
smp = new Semaphore(0, 1, smpName, out created);
if(created == false)
{
Console.WriteLine("Creation failed.");
return;
}
}
// 終わったら解放
smp.Dispose();
もし、すでにプロセスが起動しており、すでに実行中プロセスをキルしたい場合は、下記のようにプロセスIDが別のプロセスをキルするようにします。
Process curProc = Process.GetCurrentProcess();
Process[] allProc = Process.GetProcessesByName(curProc.ProcessName);
foreach(Process check in allProc)
{
if(check.Id != curProc.Id)
{
// 完全パスが同一の資産を対象としています。
if(string.Compare(check.MainModule.FileName, curProc.MainModule.FileName, true) == 0)
{
checkProcess.Kill();
}
}
}
プロセスをキルした後は、通常処理を実行するようにします。
これで、同一資産の多重起動を防げます。
基本的に、TryCatchしておけば、実行中や実行状態のまま次のモジュールを実行することなどそうそうないですが、大規模データなので、処理時間が大幅にかかってしまった場合などで役立つかもしれません。
メインとサブモジュールが分かれていて、サブモジュールを並列起動する場合は、下記のようにReleaseします。
(MainModule)
public static Main()
{
Semaphore smp = new Semaphore(0, 2, "SampleName");
for(int i = 0; i < 5; i++)
{
Console.ReadKey();
smp.Release(2);
}
}
(SubModule)
public static Main()
{
if(Semaphore.TryOpenExisting("SampleName", out Semaphore smp) == true)
{
smp.WaitOne();
// この時点でカウンタが-1されます
}
}
MainModuleを起動した状態で、SubModuleを10こ程度実行させると、すべてのSubModuleは待ち状態となります。
この状態でMainModule側でキーを押すと、カウンタが2つリリースされるので、2つのSubModuleで処理が実行されます。
このように、並列処理の場合はReleaseで制御します。
最後に
他にもMutexやlockの考え方もありますが、Semaphoreはかなりシンプルなので使えると便利です。