Categories: Cシャープ

C#のスレッドの使い方を徹底解説|マルチスレッド処理の基本から応用まで

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を更新すると例外が発生するため、InvokeDispatcherを使ってメインスレッドに処理を渡す必要があります。

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といった非同期処理が主流になりつつありますが、
スレッドの仕組みを理解しておくことは、より高度なプログラム開発の基礎として重要です。

upandup

Web制作の記事を中心に、暮らし、ビジネスに役立つ情報を発信します。 アフィリエイトにも参加しています。よろしくお願いいたします。