C#でプログラムを作成する際、「処理が重くてアプリが固まる」といった経験をしたことはありませんか?
そんなときに活躍するのが「スレッド(Thread)」です。
スレッドを使えば、複数の処理を同時に実行できるため、ユーザーにとってストレスのない操作感を実現できます。
この記事では、C#におけるスレッドの基本的な使い方から、実践的なサンプルコードまでを詳しく解説します。
マルチスレッドに初めて触れる方でも理解しやすいよう、順を追って説明していきます。
スレッドとは何か?
スレッドとは、プロセス内で動作する「軽量な実行単位」のことです。
通常、C#アプリケーションは1つのスレッド(メインスレッド)で動作しますが、
重い処理をメインスレッドに任せてしまうと、UIが固まるなどの問題が発生します。
このような問題を解決するために、処理を別のスレッドで実行することが重要になります。
スレッドを使うことで、非同期的に複数の処理を実行でき、アプリケーション全体の応答性が向上します。
スレッドの基本的な使い方(Threadクラス)
C#では、System.Threading.Threadクラスを使ってスレッドを作成できます。
まずは最も基本的なスレッドの作り方を見てみましょう。
using System;
using System.Threading;
class Program
{
    static void Main()
    {
        Thread t = new Thread(new ThreadStart(Run));
        t.Start();
        Console.WriteLine("メインスレッド終了");
    }
    static void Run()
    {
        Console.WriteLine("別スレッドで実行中");
    }
}
解説:
- Threadクラスを使って新しいスレッドを作成。
- ThreadStartデリゲートに、実行したいメソッドを指定。
- Start()でスレッドの実行を開始。
パラメータ付きのスレッドを使う方法
引数を渡したい場合には、ParameterizedThreadStartを使用します。
static void Main()
{
    Thread t = new Thread(new ParameterizedThreadStart(RunWithParameter));
    t.Start("こんにちは、スレッド!");
}
static void RunWithParameter(object message)
{
    Console.WriteLine("メッセージ: " + message);
}
注意点:
- object型で渡すため、キャストが必要な場合があります。
- 複雑なデータを渡すときは、クラスや構造体にまとめるのがベターです。
スレッドの状態を制御する(Sleep、Join、IsAlive)
スレッド制御には以下のようなメソッドやプロパティがあります。
Thread t = new Thread(() =>
{
    Console.WriteLine("開始");
    Thread.Sleep(2000); // 2秒停止
    Console.WriteLine("終了");
});
t.Start();
t.Join(); // スレッドの終了を待つ
Console.WriteLine("すべての処理が終了しました");
- Sleep(ms):指定時間スレッドを停止。
- Join():そのスレッドが終わるまで待機。
- IsAlive:スレッドがまだ動いているかどうか。
スレッドとUIの関係(WinForms/WPF)
UIアプリケーションでは、UI部品の更新はメインスレッドのみが許可されています。
サブスレッドから直接UIを更新すると例外が発生するため、InvokeやDispatcherを使ってメインスレッドに処理を渡す必要があります。
WinFormsの場合
this.Invoke((MethodInvoker)(() =>
{
    label1.Text = "更新されました";
}));
WPFの場合
Dispatcher.Invoke(() =>
{
    label1.Content = "更新されました";
});
ThreadではなくTaskを使うべきケース
.NETでは、より扱いやすい非同期プログラミングのためにTaskクラスが用意されています。
Task.Run(() =>
{
    Console.WriteLine("非同期タスクで処理中");
});
- タスクはスレッドよりも高レベルな抽象化。
- async/await構文との連携が可能。
- スレッドプールを使用するため、効率的。
実際の開発では、Taskやasync/awaitの方が推奨されるケースが多いです。
マルチスレッド時の注意点(排他制御とロック)
複数のスレッドが同じデータにアクセスする場合、データ破損を防ぐために排他制御が必要です。
private static object lockObj = new object();
static void SafeMethod()
{
    lock (lockObj)
    {
        // 共有資源へのアクセス
        Console.WriteLine("ロック中の処理");
    }
}
- lockキーワードで排他制御を行います。
- ロック競合を避ける設計が重要。
実践!重い処理をバックグラウンドスレッドで動かす
例えばファイルの読み込みやデータベースの検索など、重い処理は別スレッドで行うのが定石です。
Thread backgroundThread = new Thread(() =>
{
    // 時間のかかる処理
    Thread.Sleep(5000);
    Console.WriteLine("処理完了");
});
backgroundThread.IsBackground = true;
backgroundThread.Start();
- IsBackground = trueとすることで、メインスレッド終了時にバックグラウンドスレッドも終了します。
まとめ|スレッドを正しく使って快適なアプリに
C#でスレッドを使うことで、アプリケーションの処理を効率的に分担し、ユーザーのストレスを減らすことができます。
ただし、スレッドは便利である一方、状態管理や排他制御をしっかり行わないと、バグやデッドロックの原因になります。
最近では、Taskやasync/awaitといった非同期処理が主流になりつつありますが、
スレッドの仕組みを理解しておくことは、より高度なプログラム開発の基礎として重要です。

