Share via


スレッドおよびスレッド処理

オペレーティング システムは、実行中の各アプリケーションをプロセスを使用して分離します。 スレッドとは、オペレーティング システムがプロセッサ時間を割り当てる対象の基本単位です。複数のスレッドが、そのプロセス内でコードを実行できます。 各スレッドは、例外ハンドラー、スケジューリングの優先順位、およびスレッド コンテキストをスケジュールされるまでの間保存するために使用する一連の構造体を保持します。 スレッド コンテキストは、スレッドがシームレスに実行を再開するために必要な、スレッドの CPU レジスタおよびスタックのセットを含むすべての情報を、該当するホスト プロセスのアドレス空間に格納します。

.NET Framework は、オペレーティング システム プロセスを、System.AppDomain で表されるアプリケーション ドメインと呼ばれる軽量なマネージ サブプロセスへとさらに分割します。 System.Threading.Thread で表される 1 つ以上のマネージ スレッドが、同じマネージ プロセス内にある任意の数のアプリケーション ドメインで実行できます。 各アプリケーション ドメインは最初はシングル スレッドですが、そのアプリケーション ドメイン内のコードで追加のアプリケーション ドメインやスレッドを作成できます。 そのため、マネージ スレッドは、同じマネージ プロセス内のアプリケーション ドメイン間を自由に移動できます。1 つのスレッドだけを複数のアプリケーション ドメイン間で移動させることもできます。

プリエンプティブなマルチタスクをサポートするオペレーティング システムは、複数のプロセスからの複数のスレッドを同時に実行できます。 システムは、使用できるプロセッサ時間をこれを必要とするスレッド間で分割し、各スレッドにプロセッサ タイム スライスを順次割り当てることによってこれを実現します。 現在実行中のスレッドはタイム スライスが経過すると中断され、別のスレッドが実行を再開します。 システムは、スレッドを切り替えるときに、切り替える前のスレッドのスレッド コンテキストを保存し、スレッド キュー内にある次のスレッドの保存済みスレッド コンテキストを再読み込みします。

タイム スライスの長さは、オペレーティング システムやプロセッサによって異なります。 各タイム スライスは短いため、プロセッサが 1 つしか存在しない場合でも、複数のスレッドが同時に実行しているように見えます。 マルチプロセッサ システムでは、実行可能スレッドが複数の有効なプロセッサに分散されているため、実際に複数のスレッドが同時に実行されます。

複数のスレッドを使用する場合

ユーザーとの対話を必要とするソフトウェアは、ユーザーのアクティビティにできるだけ迅速に反応して、充実したパフォーマンスを提供する必要があります。 ただし、同時にこのソフトウェアは、ユーザーにデータを表示するために必要な計算をできるだけ高速に行う必要もあります。 アプリケーションが 1 つの実行スレッドだけを使用する場合は、非同期プログラミング.NET Framework リモート処理、または ASP.NET を使用して作成された XML Web サービスと組み合わせることで、使用しているコンピューターの処理時間だけでなく他のコンピューターの処理時間も利用できます。これにより、ユーザーへの応答を速くし、アプリケーションのデータ処理時間を短縮できます。 また、多くの I/O 操作を行う場合は、I/O 完了ポートを使用すると、アプリケーションの応答を速くすることができます。

複数のスレッドを使用するメリット

ユーザーへの応答の高速化と、ジョブの実行に必要なデータの処理をほぼ同時に実現できる最も強力な方法は、複数のスレッドを使用することです。 シングル プロセッサのコンピューターで複数のスレッドを使用すると、ユーザー イベント間の短い時間を利用してバックグラウンドでデータが処理されることによって、この効果が得られます。 たとえば、別のスレッドが同じアプリケーション内でスプレッドシートのほかの部分を再計算している間に、そのスプレッドシートを編集できます。

同じアプリケーションをマルチ プロセッサのコンピューターで実行すると、変更しなくてもパフォーマンスが大幅に向上します。 単一のアプリケーション ドメインで複数のスレッドを使用すると、次のタスクを実行できます。

  • ネットワーク経由での Web サーバーやデータベースとの通信。

  • 時間のかかる処理の実行。

  • さまざまな優先順位のタスクの区別。 たとえば、優先順位の高いスレッドは速度が重視されるタスクを管理し、優先順位の低いスレッドはほかのタスクを実行します。

  • ユーザー インターフェイスによる迅速な応答と、バックグラウンド タスクへの割り当て時間の両立。

複数のスレッドを使用するデメリット

オペレーティング システム リソースの使用を最小にしてパフォーマンスを向上させるには、使用するスレッドをできるだけ少なくすることをお勧めします。 また、アプリケーションをデザインするときは、スレッド処理にはリソース要件があること、競合が発生する可能性があることを考慮する必要があります。 リソース要件は次のとおりです。

  • システムは、プロセス、AppDomain オブジェクト、およびスレッドが必要とするコンテキスト情報を格納するためにメモリを消費します。 そのため、使用できるメモリの量によって、作成できるプロセス、AppDomain オブジェクト、およびスレッドの数は制限されます。

  • 多数のスレッドを追跡するには、プロセッサ時間を大量に消費します。 スレッドが多すぎると、ほとんどのスレッドで処理に重大な影響がでます。 現在のスレッドのほとんどが 1 つのプロセスにある場合は、ほかのプロセスにあるスレッドのスケジュール頻度が低下します。

  • 多数のスレッドを使用したコードの実行は制御が複雑なため、多くのバグの原因となる可能性があります。

  • スレッドを破棄するには、発生する可能性がある問題を認識して対処する必要があります。

リソースへの共有アクセスを提供すると、競合が発生する場合があります。 競合を回避するには、共有リソースへのアクセスとの同期をとるか、またはアクセスを制御する必要があります。 同じアプリケーション ドメインまたは異なるアプリケーション ドメインで、アクセスを正しく同期することに失敗すると、デッドロック (2 つのスレッドがお互いの完了を待って応答しなくなる状態) や競合状態 (2 つのイベントのタイミングによって予測できない動作が生じたために、異常な結果が発生した場合) などの問題が生じる可能性があります。 システムには、複数のスレッド間でのリソース共有を調整するための同期オブジェクトが用意されています。 スレッド数を減らすことで、より簡単にリソースの同期がとれるようになります。

同期を必要とするリソースは、次のとおりです。

  • システム リソース (通信ポートなど)

  • 複数のプロセスによって共有されるリソース (ファイル ハンドルなど)

  • 複数のスレッドによってアクセスされる単一のアプリケーション ドメインのリソース (グローバル フィールド、静的フィールド、インスタンス フィールドなど)

スレッド処理とアプリケーションのデザイン

一般に、他のスレッドをブロックしない比較的短いタスクを特定のスケジューリングを指定せずに実行する場合、複数のスレッドを処理する最も簡単な方法は、ThreadPool クラスを使用することです。 ただし、次に示すように、独自のスレッドを作成した方がよい場合も多くあります。

  • タスクに特定の優先順位を設定する必要がある場合。

  • タスクが長時間実行して、ほかのタスクをブロックする可能性がある場合。

  • スレッドをシングルスレッド アパートメント内に配置する必要がある場合 (すべての ThreadPool スレッドがマルチスレッド アパートメント内にある場合)。

  • スレッドに関連付けられた、確立された ID が必要な場合。 たとえば、そのスレッドをアボートまたは中断したり、名前で探索したりするには、専用のスレッドを使用する必要があります。

  • ユーザー インターフェイスと対話するバックグラウンド スレッドを実行する必要がある場合、.NET Framework Version 2.0 では、イベントを使用し、ユーザー インターフェイス スレッドへのスレッド間マーシャリングによって通信する BackgroundWorker コンポーネントを利用できます。

スレッド処理と例外

スレッドで例外を処理します。 スレッドで処理できない例外が発生すると、バックグラウンド スレッドの場合でも、通常、プロセスが終了します。 この規則には、次の 3 つの例外があります。

  • Abort が呼び出されると、スレッドで ThreadAbortException がスローされる。

  • アプリケーション ドメインがアンロードされるため、スレッドに AppDomainUnloadedException がスローされる。

  • 共通言語ランタイムまたはホスト プロセスがスレッドを終了する。

詳細については、「マネージ スレッドの例外」を参照してください。

メモメモ

.NET Framework Version 1.0 および 1.1 では、共通言語ランタイムが、スレッド プール スレッドなどで一部の例外を通知なしにトラップします。このため、アプリケーション状態が破損し、最終的にアプリケーションが停止することにより、デバッグが困難になることがあります。

参照

参照

ThreadPool

BackgroundWorker

概念

マルチスレッド処理のためのデータの同期

マネージ スレッド プール