C# Mutexの使い方を初心者向けに解説|排他制御・二重起動防止・lockとの違いまでわかる

はじめに

C#で複数のスレッドや複数のプロセスから同じ処理を実行すると、ファイルの書き込みが競合したり、同じアプリが二重に起動したりすることがあります。

このような問題を防ぐために使われる仕組みの1つが Mutex です。

C# Mutexは、排他制御を行うための同期オブジェクトです。特に、lockでは対応しにくい「プロセスをまたいだ制御」や「アプリの二重起動防止」でよく使われます。

この記事では、C# Mutexの基本的な使い方から、WaitOneReleaseMutex、名前付きMutex、二重起動防止、lockとの違いまで、初心者向けにわかりやすく解説します。

1. C#のMutexとは?初心者向けに基本を整理

1-1. Mutexは「同時に1つだけ実行させる」ための排他制御の仕組み

Mutexとは、複数のスレッドやプロセスが同じ処理やリソースに同時アクセスしないように制御するための仕組みです。

Mutexは「Mutual Exclusion」の略で、日本語では「相互排他」と呼ばれます。

たとえば、複数の処理が同時に同じファイルへ書き込むと、内容が壊れたり、意図しない順番でログが出力されたりする可能性があります。

このような場面でMutexを使うと、ある処理がリソースを使用している間、他の処理は待機します。そして、最初の処理が終わってMutexを解放すると、次の処理が実行できるようになります。

イメージとしては、1つしかない鍵を順番に受け渡すようなものです。鍵を持っている処理だけが中に入ることができ、鍵を持っていない処理は待つ必要があります。

1-2. Mutexが必要になる場面:共有ファイル・ログ出力・二重起動防止

C# Mutexは、主に次のような場面で使われます。

同じファイルへ複数の処理から書き込む場合、Mutexを使うことで同時書き込みを防げます。特にログファイルや設定ファイルなど、複数のスレッドやアプリから触られる可能性があるファイルでは有効です。

また、複数スレッドから同じ共有データを更新する場合にも、Mutexで処理を1つずつ実行させることができます。

さらに、名前付きMutexを使うと、同じアプリケーションがすでに起動しているかどうかを判定できます。そのため、C#ではアプリの二重起動防止にもMutexがよく使われます。

ただし、同一プロセス内の単純なスレッド同期であれば、Mutexよりもlockを使う方が一般的です。Mutexは便利ですが、常に最初に選ぶべきものではありません。

1-3. System.Threading.Mutexの基本的な役割

C#でMutexを使う場合は、System.Threading.Mutexクラスを利用します。

Mutexクラスは、あるスレッドまたはプロセスがリソースを占有している間、他のスレッドやプロセスが同じリソースに入れないように制御します。

基本的な流れは次のとおりです。

C#
using System.Threading;

Mutex mutex = new Mutex();

mutex.WaitOne();

try
{
// 同時に実行されたくない処理
}
finally
{
mutex.ReleaseMutex();
}

WaitOneでMutexを取得し、処理が終わったらReleaseMutexで解放します。

重要なのは、WaitOneしたら必ずReleaseMutexすることです。解放し忘れると、他のスレッドやプロセスが待ち続けてしまう可能性があります。

1-4. 名前なしMutexと名前付きMutexの違い

Mutexには、大きく分けて「名前なしMutex」と「名前付きMutex」があります。

名前なしMutexは、主に同一プロセス内で使います。

C#
var mutex = new Mutex();

このように作成したMutexは、基本的にそのアプリ内で保持して使います。

一方、名前付きMutexは、名前を付けて作成します。

C#
var mutex = new Mutex(false, "MyApplicationMutex");

名前付きMutexは、同じ名前を指定すれば別プロセスからも同じMutexを参照できます。そのため、プロセス間の排他制御やアプリの二重起動防止に使えます。

たとえば、アプリAとアプリBが同じ名前のMutexを使えば、別々のプロセスであっても同じリソースへのアクセスを制御できます。

C# Mutexを学ぶうえでは、この「名前付きMutex」が非常に重要です。

1-5. Mutexでできること・できないこと

Mutexでできることは、主に排他制御です。

たとえば、次のようなことができます。

複数スレッドから同じ処理に入るのを防ぐ、複数プロセスから同じファイルへ同時アクセスするのを防ぐ、アプリケーションの二重起動を防ぐ、といった用途です。

一方で、Mutexは万能ではありません。

Mutexを使っても、設計が悪ければデッドロックは発生します。また、Mutexはlockに比べると重い仕組みなので、同一プロセス内の単純な排他制御に多用すると、必要以上に処理が複雑になることがあります。

また、Mutexは「処理を順番に実行させる」ための仕組みであり、データの整合性を自動的に保証してくれるわけではありません。どの範囲を保護するのか、どのタイミングで解放するのかを正しく設計する必要があります。

2. C# Mutexの基本的な使い方

2-1. Mutexを使うために必要な名前空間

C#でMutexを使うには、次の名前空間を指定します。

C#
using System.Threading;

.NET 6以降のプロジェクトなどでは、暗黙的なusingによって明示的に書かなくても使える場合があります。ただし、初心者のうちはSystem.Threadingに含まれるクラスだと覚えておくとよいでしょう。

2-2. Mutexインスタンスを作成する基本コード

もっともシンプルなMutexの作成方法は次のとおりです。

C#
Mutex mutex = new Mutex();

これで名前なしMutexを作成できます。

名前付きMutexを作成する場合は、次のように名前を指定します。

C#
Mutex mutex = new Mutex(false, "SampleMutex");

第1引数のfalseは、作成時にMutexをすぐ所有するかどうかを表します。falseの場合、作成した時点では所有せず、後でWaitOneを呼び出して取得します。

第2引数はMutexの名前です。同じ名前を指定すれば、別のプロセスからも同じMutexを参照できます。

2-3. WaitOneでロックを取得する

Mutexを使って排他制御を行うには、まずWaitOneを呼び出します。

C#
mutex.WaitOne();

WaitOneは、Mutexを取得できるまで待機します。

すでに他のスレッドやプロセスがMutexを所有している場合、現在の処理はそこで待ちます。Mutexが解放されると、待機していた処理のいずれかがMutexを取得して先に進みます。

つまり、WaitOne以降の処理は「Mutexを取得できた処理だけが実行できる領域」になります。

C#
mutex.WaitOne();

// ここは同時に1つの処理だけが実行できる
Console.WriteLine("Mutexを取得しました");

2-4. ReleaseMutexでロックを解放する

WaitOneで取得したMutexは、処理が終わったらReleaseMutexで解放します。

C#
mutex.ReleaseMutex();

Mutexを解放すると、待機していた他のスレッドやプロセスがMutexを取得できるようになります。

重要なのは、Mutexを取得したスレッドがReleaseMutexを呼び出す必要があるという点です。所有していないスレッドがReleaseMutexを呼び出すと例外が発生します。

C#
mutex.WaitOne();

Console.WriteLine("処理中...");

mutex.ReleaseMutex();

このように書くこともできますが、実際のコードでは例外が発生した場合にReleaseMutexが呼ばれない危険があります。そのため、通常はtry-finallyを使います。

2-5. try-finallyで確実にReleaseMutexする書き方

Mutexを使うときは、try-finallyで確実に解放するのが基本です。

C#
mutex.WaitOne();

try
{
Console.WriteLine("排他制御したい処理を実行します");
}
finally
{
mutex.ReleaseMutex();
}

tryブロック内で例外が発生しても、finallyブロックは実行されます。

そのため、ReleaseMutexfinallyに書いておけば、例外発生時でもMutexを解放できます。

C# Mutexでは、この書き方が非常に重要です。ReleaseMutexの呼び忘れは、処理が止まる原因になります。

2-6. Dispose・usingでリソースを正しく解放する

MutexはOSの同期オブジェクトを扱うため、使い終わったらDisposeでリソースを解放します。

C#
mutex.Dispose();

より安全で読みやすい書き方として、usingを使うこともできます。

C#
using Mutex mutex = new Mutex();

mutex.WaitOne();

try
{
Console.WriteLine("処理を実行します");
}
finally
{
mutex.ReleaseMutex();
}

usingを使うと、スコープを抜けたタイミングで自動的にDisposeが呼ばれます。

ただし、DisposeはMutexオブジェクトのリソースを解放するものであり、ReleaseMutexの代わりではありません。Mutexを取得した場合は、基本的にReleaseMutexで解放してからDisposeする必要があります。

3. サンプルコードで学ぶMutexによる排他制御

3-1. 最小構成のMutexサンプルコード

まずは、最小構成のC# Mutexサンプルを見てみましょう。

C#
using System;
using System.Threading;

class Program
{
static void Main()
{
using Mutex mutex = new Mutex();

mutex.WaitOne();

try
{
Console.WriteLine("Mutexを取得しました");
Console.WriteLine("排他制御された処理を実行中です");
}
finally
{
mutex.ReleaseMutex();
Console.WriteLine("Mutexを解放しました");
}
}
}

このコードでは、WaitOneでMutexを取得し、処理が終わったらReleaseMutexで解放しています。

単体で実行するとありがたみは少ないですが、複数スレッドや複数プロセスから同じ処理を実行する場面では効果がわかりやすくなります。

3-2. 複数スレッドから同じ処理を実行する例

次に、複数スレッドから同じ処理を実行する例を見てみます。

C#
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
private static readonly Mutex Mutex = new Mutex();

static void Main()
{
Task[] tasks = new Task[3];

for (int i = 0; i < tasks.Length; i++)
{
int taskId = i + 1;
tasks[i] = Task.Run(() => DoWork(taskId));
}

Task.WaitAll(tasks);

Mutex.Dispose();

Console.WriteLine("すべての処理が完了しました");
}

static void DoWork(int id)
{
Console.WriteLine($"Task{id}: WaitOne前");

Mutex.WaitOne();

try
{
Console.WriteLine($"Task{id}: 処理開始");
Thread.Sleep(2000);
Console.WriteLine($"Task{id}: 処理終了");
}
finally
{
Mutex.ReleaseMutex();
}
}
}

このコードでは、3つのタスクが同じDoWorkメソッドを実行します。

しかし、Mutex.WaitOne()からReleaseMutex()までの間は、同時に1つのタスクしか実行できません。

実行すると、各タスクの処理開始と処理終了が順番に表示されます。これにより、Mutexによって排他制御されていることが確認できます。

3-3. ファイル書き込みをMutexで保護する例

Mutexは、ファイル書き込みの保護にも使えます。

C#
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class Program
{
private static readonly Mutex Mutex = new Mutex();
private static readonly string FilePath = "log.txt";

static void Main()
{
Task[] tasks = new Task[5];

for (int i = 0; i < tasks.Length; i++)
{
int id = i + 1;
tasks[i] = Task.Run(() => WriteLog(id));
}

Task.WaitAll(tasks);

Mutex.Dispose();

Console.WriteLine("ログ出力が完了しました");
}

static void WriteLog(int id)
{
Mutex.WaitOne();

try
{
string message = $"{DateTime.Now:HH:mm:ss.fff} Task{id} がログを書き込みました";
File.AppendAllText(FilePath, message + Environment.NewLine);
Console.WriteLine(message);
}
finally
{
Mutex.ReleaseMutex();
}
}
}

この例では、複数のタスクが同じlog.txtに書き込もうとします。

Mutexを使うことで、ファイル書き込み処理が同時に実行されないようにしています。

ただし、単純なログ出力であれば、実際の開発ではロギングライブラリを使う方が適切な場合もあります。Mutexはあくまで排他制御の仕組みとして理解しておきましょう。

3-4. WaitOneにタイムアウトを設定する例

WaitOneは、引数なしで呼び出すとMutexを取得できるまで待ち続けます。

C#
mutex.WaitOne();

しかし、待ち続けるとアプリが固まったように見えることがあります。そのため、必要に応じてタイムアウトを設定します。

C#
bool acquired = mutex.WaitOne(TimeSpan.FromSeconds(3));

if (!acquired)
{
Console.WriteLine("Mutexを取得できませんでした");
return;
}

try
{
Console.WriteLine("Mutexを取得して処理を開始します");
}
finally
{
mutex.ReleaseMutex();
}

WaitOneにタイムアウトを指定すると、指定時間内にMutexを取得できたかどうかをboolで受け取れます。

trueなら取得成功、falseならタイムアウトです。

タイムアウトを使うことで、無限待機を避けられます。実務では、ユーザー操作を伴うアプリやサービス処理で特に重要です。

3-5. 実行結果からMutexの動きを確認する

複数スレッドのサンプルを実行すると、次のような結果になります。

Task1: WaitOne前
Task2: WaitOne前
Task3: WaitOne前
Task1: 処理開始
Task1: 処理終了
Task2: 処理開始
Task2: 処理終了
Task3: 処理開始
Task3: 処理終了
すべての処理が完了しました

WaitOne前は複数のタスクでほぼ同時に表示されることがあります。

しかし、処理開始から処理終了までは、必ず1つずつ順番に実行されます。

これがMutexによる排他制御です。

実行順序は毎回同じとは限りません。どのスレッドが先にMutexを取得するかは、実行タイミングによって変わる可能性があります。

4. C# Mutexでアプリの二重起動を防止する方法

4-1. 二重起動防止に名前付きMutexを使う理由

C# Mutexの代表的な使い方の1つが、アプリケーションの二重起動防止です。

二重起動防止では、名前付きMutexを使います。

名前なしMutexは、同一プロセス内でしか共有できません。アプリが2回起動された場合、それぞれは別プロセスとして動作します。そのため、名前なしMutexでは別プロセスの起動状態を判定できません。

一方、名前付きMutexは、同じ名前を使うことでプロセスをまたいで共有できます。

つまり、1つ目のアプリが名前付きMutexを作成して保持していれば、2つ目のアプリは「同じ名前のMutexがすでに存在する」と判断できます。

4-2. createdNewを使って既に起動中か判定する

名前付きMutexで二重起動を防ぐときは、createdNewを使います。

C#
bool createdNew;
using Mutex mutex = new Mutex(true, "MyUniqueApplicationName", out createdNew);

if (!createdNew)
{
Console.WriteLine("アプリはすでに起動しています");
return;
}

Console.WriteLine("アプリを起動します");

createdNewは、新しくMutexが作成されたかどうかを表します。

trueの場合は、まだ同じ名前のMutexが存在していないため、アプリを起動してよいと判断できます。

falseの場合は、すでに同じ名前のMutexが存在しているため、アプリは起動済みと判断できます。

4-3. コンソールアプリで二重起動を防止するサンプル

コンソールアプリで二重起動を防止するサンプルは次のとおりです。

C#
using System;
using System.Threading;

class Program
{
static void Main()
{
const string mutexName = "MyCompany.MyProduct.SampleApp";

using Mutex mutex = new Mutex(true, mutexName, out bool createdNew);

if (!createdNew)
{
Console.WriteLine("このアプリケーションはすでに起動しています。");
return;
}

try
{
Console.WriteLine("アプリケーションを起動しました。");
Console.WriteLine("終了するにはEnterキーを押してください。");
Console.ReadLine();
}
finally
{
mutex.ReleaseMutex();
}
}
}

このコードでは、アプリ起動時に名前付きMutexを作成します。

すでに同じ名前のMutexが存在する場合は、createdNewfalseになるため、メッセージを表示して終了します。

最初に起動したアプリは、終了するまでMutexを保持します。これにより、2つ目の起動を防止できます。

4-4. WinForms・WPFアプリで二重起動を防止する考え方

WinFormsやWPFでも、考え方はコンソールアプリと同じです。

アプリケーションのエントリーポイントで名前付きMutexを作成し、すでに起動中であれば画面を表示せずに終了します。

WinFormsの場合は、Program.csMainメソッドで判定するのが一般的です。

C#
using System;
using System.Threading;
using System.Windows.Forms;

internal static class Program
{
[STAThread]
static void Main()
{
const string mutexName = "MyCompany.MyProduct.WinFormsApp";

using Mutex mutex = new Mutex(true, mutexName, out bool createdNew);

if (!createdNew)
{
MessageBox.Show("このアプリケーションはすでに起動しています。");
return;
}

try
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
finally
{
mutex.ReleaseMutex();
}
}
}

WPFの場合も、アプリの起動処理の早い段階でMutexを作成して判定します。

重要なのは、アプリが動作している間、Mutexオブジェクトを破棄しないことです。途中で破棄すると、二重起動防止の効果がなくなる可能性があります。

4-5. Mutex名の付け方と衝突を避けるポイント

名前付きMutexの名前は、他のアプリと衝突しないようにする必要があります。

悪い例は次のような名前です。

MyMutex
SampleApp
Test

このような短く一般的な名前は、他のアプリと衝突する可能性があります。

おすすめは、会社名、製品名、アプリ名などを組み合わせた一意性の高い名前です。

MyCompany.MyProduct.MyApplication
com.example.myapp.singleinstance

より確実にしたい場合は、GUIDを含める方法もあります。

MyCompany.MyProduct.MyApplication.{D6F3A8B2-1234-4C56-ABCD-1234567890AB}

Mutex名は、アプリケーション全体で一意になるように設計しましょう。

4-6. Global\・Local\を使うケースと注意点

Windows環境では、名前付きMutexの名前にGlobal\Local\を付けることがあります。

C#
const string mutexName = @"Global\MyCompany.MyProduct.MyApp";

Global\を使うと、ターミナルサービスなどの複数セッションをまたいで共有されます。

一方、Local\を使うと、現在のセッション内で共有されます。

通常のデスクトップアプリで「同じユーザーセッション内の二重起動を防ぎたい」だけであれば、Global\を使わなくても十分な場合が多いです。

ただし、サービスやリモートデスクトップ環境など、セッションをまたいだ制御が必要な場合はGlobal\を検討します。

注意点として、Global\を使う場合は権限の問題が発生することがあります。特に、サービスと通常ユーザーアプリの間で共有する場合などは、アクセス権の設定が必要になるケースがあります。

初心者のうちは、まず一意な名前の名前付きMutexを使い、必要になったらGlobal\や権限設定を検討するとよいでしょう。

5. Mutexとlockの違いをわかりやすく比較

5-1. lockは同一プロセス内のスレッド同期に向いている

C#には、Mutex以外にも排他制御の仕組みがあります。その代表がlockです。

lockは、同一プロセス内の複数スレッドを同期するためによく使われます。

C#
private static readonly object LockObject = new object();

lock (LockObject)
{
// 同時に実行されたくない処理
}

lockは書き方が簡単で、処理も軽量です。

同じアプリ内の複数スレッドから共有変数を更新する、リストへの追加を保護する、といった場面では、まずlockを検討するのが一般的です。

5-2. Mutexはプロセスをまたいだ排他制御に使える

Mutexとlockの大きな違いは、プロセスをまたいで使えるかどうかです。

lockは、基本的に同じプロセス内でしか使えません。別々に起動したアプリ同士をlockで同期することはできません。

一方、名前付きMutexを使えば、別プロセス間でも同じMutexを共有できます。

そのため、次のような場合はMutexが向いています。

複数のアプリから同じファイルにアクセスする、同じアプリの二重起動を防止する、別プロセス間で処理の実行タイミングを制御する、といったケースです。

5-3. 処理速度・用途・書きやすさの違い

lockとMutexには、処理速度や用途の違いがあります。

lockは軽量で、コードも短く書けます。同一プロセス内のスレッド同期であれば、多くの場合lockで十分です。

MutexはOSレベルの同期オブジェクトを扱うため、lockより重くなりやすいです。その代わり、名前付きMutexを使えばプロセス間の排他制御ができます。

簡単に整理すると、同じアプリ内だけならlock、別プロセスも含めて制御したいならMutexです。

初心者がC# Mutexを使うときは、「lockでできることをわざわざMutexで書いていないか」を確認するとよいでしょう。

5-4. lockで十分なケース

次のようなケースでは、Mutexではなくlockで十分です。

同じアプリ内の複数スレッドから共有変数を更新する場合、メモリ上のリストや辞書へのアクセスを保護する場合、プロセス間共有が不要な場合です。

たとえば、次のような処理はlockで十分です。

C#
private static readonly object LockObject = new object();
private static int Counter = 0;

static void Increment()
{
lock (LockObject)
{
Counter++;
}
}

このような単純なスレッド同期にMutexを使うと、コードがやや大げさになります。

5-5. Mutexを選ぶべきケース

Mutexを選ぶべきなのは、プロセスをまたいだ制御が必要な場合です。

たとえば、次のようなケースです。

アプリの二重起動を防止したい、複数プロセスから同じファイルを書き込みたい、別アプリ同士で同時実行を防ぎたい、Windowsサービスとデスクトップアプリで同じリソースを扱いたい、といった場合です。

このような場面では、lockでは対応できません。

名前付きMutexを使うことで、プロセス間で共通の同期オブジェクトを利用できます。

5-6. Monitor・SemaphoreSlim・Semaphoreとの違い

C#には、Mutexやlock以外にも同期の仕組みがあります。

Monitorは、lockの内部で使われている仕組みに近いものです。実際、lock文はMonitor.EnterMonitor.Exitを使ったコードに展開されます。通常は、直接Monitorを使うよりlockを使う方が読みやすいです。

SemaphoreSlimは、非同期処理でも使いやすい軽量な同期オブジェクトです。WaitAsyncが使えるため、async/awaitと相性が良いです。

C#
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);

await Semaphore.WaitAsync();

try
{
// 非同期で排他制御したい処理
}
finally
{
Semaphore.Release();
}

Semaphoreは、複数のスレッドやプロセスに対して、同時に実行できる数を制限するための仕組みです。Mutexが「1つだけ」に制限するのに対し、Semaphoreは「最大3つまで」のような制限ができます。

Mutexは、所有権を持つスレッドが解放する必要がある点も特徴です。

6. Mutexを使うときの注意点とよくあるトラブル

6-1. ReleaseMutexを呼び忘れると処理が止まる

Mutexで最も多いミスの1つが、ReleaseMutexの呼び忘れです。

C#
mutex.WaitOne();

// 例外が発生するとReleaseMutexされない可能性がある
DoSomething();

mutex.ReleaseMutex();

この書き方では、DoSomethingで例外が発生した場合、ReleaseMutexが呼ばれません。

その結果、他のスレッドやプロセスがMutexを取得できず、待ち続ける可能性があります。

必ず次のようにtry-finallyを使いましょう。

C#
mutex.WaitOne();

try
{
DoSomething();
}
finally
{
mutex.ReleaseMutex();
}

C# Mutexを使うときは、WaitOneReleaseMutexを必ずセットで考えることが重要です。

6-2. 所有していないMutexをReleaseMutexすると例外になる

Mutexは、所有しているスレッドが解放する必要があります。

所有していない状態でReleaseMutexを呼び出すと、例外が発生します。

特に、タイムアウト付きのWaitOneを使う場合は注意が必要です。

C#
bool acquired = mutex.WaitOne(TimeSpan.FromSeconds(3));

try
{
if (!acquired)
{
Console.WriteLine("Mutexを取得できませんでした");
return;
}

Console.WriteLine("処理を実行します");
}
finally
{
if (acquired)
{
mutex.ReleaseMutex();
}
}

このように、Mutexを取得できた場合だけReleaseMutexを呼ぶようにします。

WaitOnefalseを返したのにReleaseMutexを呼ぶと、所有していないMutexを解放しようとしてしまいます。

6-3. WaitOneで待ち続けてアプリが固まる問題

WaitOne()を引数なしで呼ぶと、Mutexを取得できるまで待ち続けます。

バックグラウンド処理なら問題にならない場合もありますが、UIスレッドでこれを行うと、画面が固まったように見えることがあります。

C#
mutex.WaitOne();

このような無制限の待機は、必要な場面をよく考えて使うべきです。

ユーザー操作があるアプリでは、タイムアウトを設定する、別スレッドで待機する、キャンセルできる設計にするなどの工夫が必要です。

C#
if (!mutex.WaitOne(TimeSpan.FromSeconds(5)))
{
Console.WriteLine("処理が混み合っています。後でもう一度お試しください。");
return;
}

タイムアウトを使うことで、アプリが永遠に待ち続ける問題を避けられます。

6-4. デッドロックを防ぐ設計の考え方

Mutexを使うときは、デッドロックにも注意が必要です。

デッドロックとは、複数の処理がお互いの解放を待ち続けて、どちらも進めなくなる状態です。

たとえば、処理AがMutex1を取得したままMutex2を待ち、処理BがMutex2を取得したままMutex1を待つと、両方とも進めなくなります。

デッドロックを防ぐには、次のような設計が重要です。

Mutexを取得する順番を統一する、Mutexを保持する時間を短くする、不要な範囲までMutexで囲まない、タイムアウトを設定する、といった考え方です。

特に、複数のMutexを同時に扱う場合は注意が必要です。

6-5. AbandonedMutexExceptionが発生する原因

Mutexでは、AbandonedMutexExceptionが発生することがあります。

これは、あるスレッドやプロセスがMutexを所有したまま異常終了した場合に発生する例外です。

たとえば、プロセスAがMutexを取得したままクラッシュすると、プロセスBがそのMutexを取得しようとしたときにAbandonedMutexExceptionが発生する場合があります。

この例外は、「前の所有者が正常にMutexを解放しなかった」というサインです。

ただし、例外が発生した時点で、現在のスレッドはMutexを取得している場合があります。そのため、必要に応じてデータの整合性を確認し、最後にはReleaseMutexで解放する必要があります。

C#
bool acquired = false;

try
{
try
{
acquired = mutex.WaitOne(TimeSpan.FromSeconds(5));
}
catch (AbandonedMutexException)
{
acquired = true;
Console.WriteLine("前回の処理が異常終了した可能性があります。");
}

if (!acquired)
{
Console.WriteLine("Mutexを取得できませんでした。");
return;
}

Console.WriteLine("保護された処理を実行します。");
}
finally
{
if (acquired)
{
mutex.ReleaseMutex();
}
}

AbandonedMutexExceptionが発生した場合は、共有ファイルや共有データが中途半端な状態になっていないか確認する設計が必要です。

6-6. 名前付きMutexの権限・セッション違いによる問題

名前付きMutexでは、権限やセッションの違いによって期待どおりに動作しないことがあります。

たとえば、通常ユーザーで起動したアプリと管理者権限で起動したアプリ、またはWindowsサービスとデスクトップアプリでは、同じ名前のMutexにアクセスできない場合があります。

また、リモートデスクトップや複数ユーザー環境では、セッションが異なるため、Local\Global\の扱いが問題になることがあります。

単純な二重起動防止であれば、まず通常の名前付きMutexで十分です。しかし、複数ユーザーやサービスを含む構成では、Mutex名のスコープやアクセス権も考慮しましょう。

6-7. async/awaitとMutexを組み合わせるときの注意点

Mutexは、async/awaitとあまり相性がよくありません。

理由は、Mutexが所有権をスレッドに結び付ける仕組みだからです。

awaitを使うと、処理の再開が別スレッドになる場合があります。そのため、WaitOneしたスレッドとReleaseMutexするスレッドが異なり、問題が起きる可能性があります。

非同期処理で排他制御をしたい場合は、SemaphoreSlimを検討するのが一般的です。

C#
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);

static async Task DoWorkAsync()
{
await Semaphore.WaitAsync();

try
{
await Task.Delay(1000);
Console.WriteLine("非同期処理を実行しました");
}
finally
{
Semaphore.Release();
}
}

プロセス間排他制御が必要な場合はMutexが候補になりますが、同一プロセス内の非同期処理であればSemaphoreSlimの方が扱いやすいです。

7. Mutexの実践的な使い分けパターン

7-1. 同一アプリ内のスレッド制御ならlockを優先する

同じアプリ内の複数スレッドを制御したいだけなら、まずlockを検討しましょう。

lockは書きやすく、読みやすく、Mutexより軽量です。

C#
private static readonly object LockObject = new object();

lock (LockObject)
{
// 同一プロセス内の排他制御
}

C# Mutexは便利ですが、すべての排他制御に使う必要はありません。

単純なスレッド同期では、lockの方が自然です。

7-2. 複数プロセス間の制御なら名前付きMutexを使う

複数プロセス間で排他制御したい場合は、名前付きMutexを使います。

C#
using Mutex mutex = new Mutex(false, "MyCompany.MyApp.SharedResource");

mutex.WaitOne();

try
{
// 複数プロセスから同時に実行されたくない処理
}
finally
{
mutex.ReleaseMutex();
}

名前付きMutexを使えば、別々に起動したアプリ同士でも同じ同期オブジェクトを共有できます。

二重起動防止や共有ファイルの保護では、名前付きMutexが有効です。

7-3. 非同期処理ではSemaphoreSlimも検討する

async/awaitを使う非同期処理では、MutexよりSemaphoreSlimが向いていることが多いです。

C#
private static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);

await Semaphore.WaitAsync();

try
{
await SomeAsyncMethod();
}
finally
{
Semaphore.Release();
}

SemaphoreSlimWaitAsyncを使えるため、非同期処理の流れを止めずに待機できます。

同一プロセス内の非同期排他であれば、MutexよりもSemaphoreSlimを優先してよい場面が多いです。

7-4. 複数リソースを扱う場合の設計ポイント

複数のリソースを扱う場合は、Mutexの設計が複雑になります。

たとえば、ファイルAとファイルBをそれぞれ別のMutexで保護する場合、取得順序がバラバラだとデッドロックの原因になります。

処理1がファイルAのMutexを取得してからファイルBを待ち、処理2がファイルBのMutexを取得してからファイルAを待つと、どちらも進めなくなる可能性があります。

このような問題を防ぐには、Mutexを取得する順番を必ず統一します。

また、Mutexで保護する範囲はできるだけ短くします。時間のかかる処理や外部API呼び出しなどをMutexの中に入れると、待機時間が長くなりやすいです。

7-5. 本番コードで意識したい例外処理とログ出力

本番コードでMutexを使う場合は、例外処理とログ出力も重要です。

特に、次のような情報をログに残すとトラブル調査がしやすくなります。

Mutexを取得できなかった、タイムアウトした、AbandonedMutexExceptionが発生した、ReleaseMutexで例外が発生した、といった情報です。

また、Mutexを取得した後の処理で例外が発生しても、必ずReleaseMutexされるようにtry-finallyを書く必要があります。

C#
bool acquired = false;

try
{
acquired = mutex.WaitOne(TimeSpan.FromSeconds(10));

if (!acquired)
{
Console.WriteLine("Mutexの取得がタイムアウトしました。");
return;
}

// 保護したい処理
}
catch (AbandonedMutexException)
{
acquired = true;
Console.WriteLine("Mutexが放棄されていました。データ状態を確認してください。");
}
finally
{
if (acquired)
{
mutex.ReleaseMutex();
}
}

本番環境では、「正常系だけ動くコード」ではなく、「異常時にも安全に解放されるコード」を書くことが大切です。

8. C# Mutexに関するよくある質問

8-1. Mutexとlockはどちらを使えばいい?

同一プロセス内のスレッド同期であれば、基本的にはlockを使うのがおすすめです。

lockは簡潔で軽量です。共有変数やリスト、辞書などを同じアプリ内で保護するだけなら、Mutexを使う必要はあまりありません。

一方、複数プロセス間で排他制御したい場合や、アプリの二重起動を防止したい場合は、名前付きMutexを使います。

判断基準はシンプルです。

同じアプリ内だけならlock、別プロセスも関係するならMutexです。

8-2. Mutexは二重起動防止以外にも使える?

はい、使えます。

C# Mutexは、二重起動防止以外にも、共有ファイルの保護、複数プロセスからのログ出力制御、別アプリ同士の同時実行防止などに使えます。

特に名前付きMutexを使うと、プロセスをまたいだ排他制御ができます。

ただし、同じプロセス内だけで完結する場合は、lockSemaphoreSlimの方が適していることもあります。

8-3. ReleaseMutexは必ず必要?

WaitOneでMutexを取得した場合は、基本的にReleaseMutexが必要です。

Mutexを取得したまま解放しないと、他のスレッドやプロセスが待ち続ける原因になります。

そのため、次のようにtry-finallyで確実に解放しましょう。

C#
mutex.WaitOne();

try
{
// 処理
}
finally
{
mutex.ReleaseMutex();
}

ただし、タイムアウト付きWaitOneで取得に失敗した場合は、ReleaseMutexを呼んではいけません。取得できた場合だけ解放します。

8-4. 名前付きMutexの名前は何でもいい?

名前付きMutexの名前は自由に決められますが、何でもよいわけではありません。

他のアプリと衝突しないように、一意性の高い名前を付ける必要があります。

おすすめは、会社名、製品名、アプリ名などを組み合わせる方法です。

MyCompany.MyProduct.MyApplication

さらに衝突を避けたい場合は、GUIDを含めるのも有効です。

短すぎる名前や一般的すぎる名前は避けましょう。

8-5. Mutexを使っても二重起動防止できない原因は?

Mutexを使っているのに二重起動防止できない場合、いくつかの原因が考えられます。

まず、Mutexオブジェクトをアプリ実行中に保持していない可能性があります。Mutexが途中で破棄されると、他のプロセスが起動できてしまいます。

次に、毎回違うMutex名を使っている可能性があります。名前付きMutexは、同じ名前を使って初めて効果があります。

また、createdNewの判定を間違えている場合もあります。createdNewfalseなら既に起動中と判断するのが基本です。

さらに、ユーザーセッションや権限の違いによって、同じ名前のMutexを共有できていない場合もあります。リモートデスクトップ環境や管理者権限での起動が絡む場合は、Global\やアクセス権の設定も確認しましょう。

8-6. Mutexは初心者でも使うべき?

Mutexは初心者でも理解しておきたい重要な仕組みです。

特に、C#でアプリの二重起動防止を実装したい場合、名前付きMutexはよく使われます。

ただし、すべての排他制御にMutexを使う必要はありません。

同一プロセス内のスレッド制御であればlock、非同期処理ならSemaphoreSlim、複数プロセス間の制御ならMutex、というように使い分けることが大切です。

初心者のうちは、まずlockとの違いを理解し、二重起動防止のサンプルから試してみるとよいでしょう。

まとめ

C# Mutexは、複数のスレッドやプロセスから同じリソースへ同時アクセスするのを防ぐための排他制御の仕組みです。

基本的な使い方は、WaitOneでMutexを取得し、処理が終わったらReleaseMutexで解放する流れです。

C#
mutex.WaitOne();

try
{
// 排他制御したい処理
}
finally
{
mutex.ReleaseMutex();
}

Mutexを使うときは、try-finallyで確実に解放することが重要です。

また、名前付きMutexを使えば、プロセスをまたいだ排他制御ができます。そのため、C#ではアプリの二重起動防止によく使われます。

一方で、同一プロセス内の単純なスレッド同期であれば、Mutexよりもlockの方が適していることが多いです。非同期処理ではSemaphoreSlimも有力な選択肢です。

C# Mutexを使いこなすポイントは、用途に応じて適切に使い分けることです。

同じアプリ内だけならlock、プロセスをまたぐなら名前付きMutex、非同期処理ならSemaphoreSlimを検討しましょう。