NET は、System.Threading 名前空間でマルチスレッド機能を定義します。したがって、マルチスレッドを使用するには、まずこの名前空間への参照を宣言する必要があります (System.Threading; を使用)。
a. スレッドを開始します。名前が示すように、「スレッドを開始する」とは、次のコードでスレッドを開始することを意味します。
スレッド thread1 = 新しいスレッド(新しい ThreadStart(Count));
ここで、Count は新しいスレッドによって実行される関数です。
b. スレッドを強制終了する
「スレッドの強制終了」とは、労力を無駄にしないように、スレッドを強制終了する前に (IsAlive 属性によって) スレッドがまだ生きているかどうかを確認し、Abort メソッドを呼び出してスレッドを強制終了することです。 。
c. スレッドの一時停止とは、実行中のスレッドを一定期間スリープ状態にすることを意味します。たとえば、thread.Sleep(1000); はスレッドを 1 秒間スリープさせます。
d. 優先順位については説明の必要はありません。 Thread クラスの hreadPriority 属性は優先順位の設定に使用されますが、オペレーティング システムが優先順位を受け入れるという保証はありません。スレッドの優先度は、Normal、AboveNormal、BelowNormal、Highest、Lowestの5種類に分類できます。具体的な実装例は以下のとおりです。
thread.Priority = ThreadPriority.Highest;
e. スレッドを一時停止する
Thread クラスの Suspend メソッドは、Resume が呼び出されるまでスレッドを一時停止してから、スレッドの実行を続行するために使用されます。スレッドがすでに一時停止されている場合、それは機能しません。
if (スレッド.ThreadState = ThreadState.Running)
{
スレッド.サスペンド();
}
f. 回復スレッドは、中断されたスレッドを再開して実行を継続できるようにするために使用されます。
if (スレッド.ThreadState = ThreadState.Suspended)
{
thread.Resume();
}
単純なスレッド機能を説明するための例を以下に示します。この例はヘルプドキュメントからのものです。
システムを使用する;
System.Threading を使用します。
// 単純なスレッド化シナリオ: 静的メソッドの実行を開始する
// 2 番目のスレッドで。
パブリック クラス ThreadExample {
// スレッドの開始時に ThreadProc メソッドが呼び出されます。
// 10 回ループし、コンソールに書き込み、結果を返します。
// 残りのタイムは毎回スライスされて終了します。
public static void ThreadProc() {
for (int i = 0; i < 10; i++) {
Console.WriteLine("ThreadProc: {0}", i);
// 残りのタイム スライスを取得します。
スレッド.スリープ(0);
}
}
public static void Main() {
Console.WriteLine("メインスレッド: 2 番目のスレッドを開始します。");
// Thread クラスのコンストラクターには ThreadStart が必要です
// 上で実行されるメソッドを表すデリゲート
// スレッド。C# はこのデリゲートの作成を簡素化します。
スレッド t = 新しいスレッド(新しい ThreadStart(ThreadProc));
// ユニプロセッサでは ThreadProc を開始します。スレッドは取得されません。
// メインスレッドが降伏するまでの任意のプロセッサ時間。
// t.Start() に続く Thread.Sleep で違いを確認します。
t.Start();
//Thread.Sleep(0);
for (int i = 0; i < 4; i++) {
Console.WriteLine("メインスレッド: いくつかの作業を行います。");
スレッド.スリープ(0);
}
Console.WriteLine("メイン スレッド: Join() を呼び出し、ThreadProc が終了するまで待機します。");
t.Join();
Console.WriteLine("メイン スレッド: ThreadProc.Join が戻りました。Enter キーを押してプログラムを終了します。");
Console.ReadLine();
}
}
このコードは、次のような出力を生成します。
メインスレッド: 2 番目のスレッドを開始します。
メインスレッド: いくつかの作業を行います。
スレッドプロセッサ: 0
メインスレッド: いくつかの作業を行います。
スレッドプロセッサ: 1
メインスレッド: いくつかの作業を行います。
スレッドプロセッサ: 2
メインスレッド: いくつかの作業を行います。
スレッドプロセッサ: 3
メインスレッド: Join() を呼び出し、ThreadProc が終了するまで待機します。
スレッドプロセッサ: 4
スレッドプロセッサ: 5
スレッドプロセッサ: 6
スレッドプロセッサ: 7
スレッドプロセッサ: 8
スレッドプロセッサ: 9
メイン スレッド: ThreadProc.Join が戻りました。Enter キーを押してプログラムを終了します。
Visul C# では、System.Threading 名前空間は、マルチスレッド プログラミングを可能にするいくつかのクラスとインターフェイスを提供します。スレッドを作成するには、Thread、ThreadPool、Timer の 3 つのメソッドがあります。ここでは、それぞれの使い方を簡単に紹介します。
1. スレッド
これはおそらく最も複雑な方法ですが、スレッドに対するさまざまな柔軟な制御を提供します。まず、そのコンストラクターを使用してスレッド インスタンスを作成する必要があります。そのパラメーターは 1 つの ThreadStart デリゲートだけであり、次に Start() を呼び出して開始することができます。実行優先度を設定または取得します (列挙型 ThreadPriority: Normal、Lowest、Highest、BelowNormal、AboveNormal)。
次の例では、最初に 2 つのスレッド インスタンス t1 と t2 を生成し、次にそれぞれの優先順位を設定してから、2 つのスレッドを開始します (出力が異なることを除いて、2 つのスレッドは基本的に同じです。t1 は "1" で、t2 は "2" です)出力される文字数の比率に応じて、それらが占める CPU 時間の比率が大まかにわかります。これはそれぞれの優先順位も反映します)。
static void Main(string[] args)
{
スレッド t1 = 新しいスレッド(新しい ThreadStart(Thread1));
スレッド t2 = 新しいスレッド(新しい ThreadStart(Thread2));
t1.Priority = ThreadPriority.BelowNormal;
t2.Priority = ThreadPriority.Lowest;
t1.Start();
t2.Start();
}
パブリック静的 void Thread1()
{
for (int i = 1; i < 1000; i++)
{//ループが実行されるたびに「1」を書き込みます。
dosth();
Console.Write("1");
}
}
パブリック静的 void Thread2()
{
for (int i = 0; i < 1000; i++)
{//ループが実行されるたびに「2」を書き込みます。
dosth();
Console.Write("2");
}
}
パブリック静的無効dosth()
{//複雑な操作をシミュレートするために使用されます
for (int j = 0; j < 10000000; j++)
{
int a=15;
a = a*a*a*a;
}
}
上記のプログラムを実行した結果は次のようになります。
1111111111111111111111111111111111111111211111111111111111111111111111111111111112
1111111111111111111111111111111111111111211111111111111111111111111111111111111112
1111111111111111111111111111111111111111211111111111111111111111111111111111111112
上記の結果から、t1 スレッドが t2 よりも多くの CPU 時間を消費していることがわかります。これは、t1 と t2 の両方の優先順位を「Normal」に設定すると、t1 の優先順位が t2 の優先順位よりも高いためです。次の写真のとおりです:
121211221212121212121212121212121212121212121212121212121212121212121
212121212121212121212121212121212121212121212121212121212121212121212
121212121212121212
上の例から、その構造は win32 ワーカー スレッドに似ていることがわかりますが、スレッドによって呼び出される関数をデリゲートとして使用し、そのデリゲートをパラメーターとして使用するだけで済みます。スレッドインスタンスを構築します。 Start() を呼び出して開始すると、対応する関数が呼び出され、その関数の最初の行から実行が開始されます。
次に、スレッドの ThreadState プロパティを組み合わせて、スレッドの制御を理解します。 ThreadState は、スレッドの状態を反映する列挙型です。 Thread インスタンスが最初に作成されたとき、その ThreadState は Unstarted ですが、Start() を呼び出してスレッドが開始されたとき、その ThreadState は Running になります。スレッドを一時停止 (ブロック) したい場合は、Thread を呼び出すことができます。 Sleep() メソッドには 2 つのオーバーロードされたメソッド (Sleep(int)、Sleep(Timespan)) があり、これらは時間を表す形式が異なるだけです。この関数がスレッド内で呼び出される場合、スレッドがブロックされることを意味します。一定期間 (時間はスリープに渡されるミリ秒数またはタイムスパンによって決まりますが、パラメーターが 0 の場合は、このスレッドを一時停止して他のスレッドが実行できるようにし、スレッドを無期限にブロックするように Infinite を指定することを意味します)。今度は ThreadState が WaitSleepJoin になります。もう 1 つの注目すべき点は、Sleep() 関数が静的として定義されていることです。 ! これは、スレッド インスタンスと組み合わせて使用できないこと、つまり t1.Sleep(10) に似た呼び出しがないことも意味します。これと同様に、Sleep() 関数は、それ自体を「スリープ」する必要があるスレッドによってのみ呼び出すことができ、いつスリープするかが個人的な問題であり、他の人には決定できないのと同様に、他のスレッドから呼び出すことはできません。 。ただし、スレッドが WaitSleepJoin 状態にあり、スレッドを起動する必要がある場合は、Thread.Interrupt メソッドを使用できます。これにより、スレッドで ThreadInterruptedException がスローされます。まず例を見てみましょう (Sleep の呼び出しメソッドに注目してください)。
static void Main(string[] args)
{
スレッド t1 = 新しいスレッド(新しい ThreadStart(Thread1));
t1.Start();
t1.割り込み();
E.WaitOne();
t1.割り込み();
t1.Join();
Console.WriteLine(“t1 は終了”);
}
静的 AutoResetEvent E = 新しい AutoResetEvent(false);
パブリック静的 void Thread1()
{
試す
{//パラメータから、冬眠を引き起こすことがわかります
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{//割り込みハンドラ
Console.WriteLine (" 1 回目の割り込み");
}
E.Set();
試す
{// 寝る
Thread.Sleep(Timeout.Infinite);
}
catch(System.Threading.ThreadInterruptedException e)
{
Console.WriteLine (" 2 回目の割り込み");
}//10秒間一時停止します
Thread.Sleep (10000);
}
実行結果は次のとおりです: 1 回目の割り込み
2回目の割り込み
(10秒後)t1が終了
上記の例から、Thread.Interrupt メソッドはプログラムをブロッキング (WaitSleepJoin) 状態からウェイクアップし、対応する割り込みハンドラーに入り、実行を継続できることがわかります (これを使用すると、ThreadState も Running に変わります)。関数は次の点に注意する必要があります。
1. このメソッドは、スリープによって引き起こされたブロックをウェイクアップできるだけでなく、スレッドを WaitSleepJoin 状態にする可能性のあるすべてのメソッド (Wait や Join など) にも有効です。上記の例にあるように、使用する場合は、スレッド ブロックを引き起こすメソッドを try ブロックに配置し、対応する割り込みハンドラーを catch ブロックに配置する必要があります。
2. 特定のスレッドで Interrupt を呼び出します。スレッドが WaitSleepJoin 状態にある場合、実行のために対応する割り込みハンドラーに入ります。この時点で WaitSleepJoin 状態にない場合は、後でこの状態になったときにすぐに中断されます。 。中断する前に Interrupt が複数回呼び出された場合、最初の呼び出しのみが有効になります。これが、最初の割り込みの後で Interrupt の 2 回目の呼び出しが呼び出されるようにするために、最初の呼び出しのみが有効になる理由です。そうしないと、2 回目の呼び出しが発生する可能性があります。この呼び出しは効果がありません (最初の割り込みの前に呼び出された場合)。同期を削除してみると、次のような結果が得られる可能性があります。1 回目の割り込み。
上記の例では、同期オブジェクトと Thread.Join メソッドを使用して、スレッドを WaitSleepJoin 状態にする他の 2 つのメソッドも使用しています。 Join メソッドの使用は比較的簡単です。つまり、このメソッドを呼び出している現在のスレッドは、他のスレッド (この例では t1) が終了するか、指定された時間が経過するまでブロックされます (時間パラメーターも必要な場合)。条件 (存在する場合) が発生すると、即座に WaitSleepJoin 状態が終了し、Running 状態に入ります (条件は .Join メソッドの戻り値に基づいて決定できます。true の場合、スレッドは終了します。false の場合、時間切れです)。 Thread.Suspend メソッドを使用してスレッドを一時停止することもできます。スレッドが実行状態にあるときに Suspend メソッドが呼び出されると、スレッドは SuspendRequested 状態になりますが、スレッドが安全な状態に達するまですぐには一時停止されません。スレッドはハングし、その時点でサスペンド状態になります。すでに一時停止されているスレッドで呼び出された場合は無効になります。操作を再開するには、Thread.Resume を呼び出すだけです。
最後にスレッドの破棄について説明します。破棄する必要があるスレッドで Abort メソッドを呼び出すと、このスレッドで ThreadAbortException がスローされます。スレッド内のコードを try ブロックに配置し、対応する処理コードを対応する catch ブロックに配置できます。スレッドが try ブロックのコードを実行しているときに、Abort が呼び出されると、対応する catch ブロックにジャンプします。 catch ブロック内で実行されると、catch ブロック内のコードを実行した後に終了します (ResetAbort が catch ブロック内で実行される場合は異なります。現在の Abort リクエストがキャンセルされ、下向きの実行が継続されます。スレッドを確実に終了させたい場合は、上記の例のように Join を使用するのが最善です)。
2. スレッドプール
スレッド プール (ThreadPool) は、複数のスレッドを必要とする短いタスク (頻繁にブロックされるスレッドなど) に適しています。欠点は、作成されたスレッドを制御できないことと、その優先順位を設定できないことです。各プロセスにはスレッド プールが 1 つだけあり、もちろん各アプリケーション ドメインにはスレッド プール (ライン) が 1 つだけあるため、ThreadPool クラスのメンバー関数はすべて静的であることがわかります。 ThreadPool.QueueUserWorkItem、ThreadPool.RegisterWaitForSingleObject などを初めて呼び出すと、スレッド プール インスタンスが作成されます。以下は、スレッド プール内の 2 つの関数の概要です。
public static bool QueueUserWorkItem( //呼び出しが成功した場合は true を返します
WaitCallback callBack,// 作成されるスレッドによって呼び出されるデリゲート
object state //デリゲートに渡されるパラメータ
)//もう 1 つのオーバーロード関数も似ていますが、デリゲートがパラメーターを受け取らない点が異なります。この関数の機能は、スレッド プール内の使用可能なスレッドの数がゼロではない場合に、作成されるスレッドをスレッド プールにキューに入れることです。スレッド プールがスレッドを作成しました。数が制限されている場合 (デフォルト値は 25)、このスレッドは作成されます。それ以外の場合は、スレッド プールのキューに入れられ、使用可能なスレッドができるまで待機します。
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject,//登録するWaitHandle
WaitOrTimerCallback callBack, // スレッド呼び出しデリゲート
オブジェクトの状態、//デリゲートに渡されるパラメータ
int TimeOut,//タイムアウト、単位はミリ秒、
boolexecuteOnlyOnce file:// 1 回だけ実行するかどうか
);
パブリックデリゲート void WaitOrTimerCallback(
オブジェクトの状態、//つまり、デリゲートに渡されるパラメータ
bool timedOut//true はタイムアウト呼び出しによることを意味し、それ以外の場合は waitObject を意味します
);
この関数の機能は、待機中のスレッドを作成することです。この関数が呼び出されると、このスレッドはパラメータ waitObject が終了状態に変化するか、設定された時刻になるまで「ブロック」状態になります。この「ブロッキング」はスレッドの WaitSleepJoin 状態とは大きく異なることに注意してください。スレッドが WaitSleepJoin 状態にある場合、CPU は定期的にスレッドを起動してステータス情報をポーリングおよび更新し、その後再び WaitSleepJoin 状態に入ります。スレッドの切り替えは非常にリソースを消費します。この関数で作成されたスレッドは、実行がトリガーされる前にこのスレッドに切り替わりませんが、CPU はどのように動作するのでしょうか。いつ実行すればよいか知っていますか?実際、スレッド プールはこれらのトリガー条件を監視するためにいくつかの補助スレッドを生成します。もちろん、条件が満たされると、これらの補助スレッド自体も開始されますが、さらに待機を作成する必要がある場合には実行されます。スレッドの場合は、スレッド プールを使用します。利点はさらに明白になります。以下の例を参照してください。
静的 AutoResetEvent ev=new AutoResetEvent(false);
public static int Main(string[] args)
{ ThreadPool.RegisterWaitForSingleObject(
エフ、
新しい WaitOrTimerCallback(WaitThreadFunc)、
4、
2000年、
false//ログアウトして待機するまで、待機操作が完了するたびにタイマーがリセットされることを示します。
);
ThreadPool.QueueUserWorkItem (新しい WaitCallback (ThreadFunc),8);
Thread.Sleep (10000);
0を返します。
}
public static void ThreadFunc(オブジェクト b)
{ Console.WriteLine ("オブジェクトは {0}",b);
for(int i=0;i<2;i++)
{ スレッド.スリープ (1000);
ev.Set();
}
}
public static void WaitThreadFunc(object b,bool t)
{ Console.WriteLine ("オブジェクトは {0}、t は {1}"、b,t);
}
その操作の結果は次のようになります。
オブジェクトは8です
オブジェクトは 4、t は False
オブジェクトは 4、t は False
オブジェクトは 4、t は True
オブジェクトは 4、t は True
オブジェクトは 4、t は True
上記の結果から、スレッド ThreadFunc が 1 回実行され、WaitThreadFunc が 5 回実行されたことがわかります。このスレッドを開始する理由は、WaitOrTimerCallback の bool t パラメータから判断できます。t が false の場合は waitObject を意味し、それ以外の場合はタイムアウトが原因です。さらに、オブジェクト b を通じていくつかのパラメータをスレッドに渡すこともできます。
3. タイマー
定期的に呼び出す必要があるメソッドに適しています。タイマーを作成したスレッドでは実行されず、システムによって自動的に割り当てられた別のスレッドで実行されます。これは、Win32 の SetTimer メソッドに似ています。その構造は次のとおりです。
パブリックタイマー(
TimerCallback コールバック、//呼び出されるメソッド
オブジェクトの状態、//コールバックに渡されるパラメータ
int dueTime,//コールバックの呼び出しを開始するまでにどれくらい時間がかかりますか?
int period//このメソッドを呼び出す時間間隔
); // dueTime が 0 の場合、コールバックは最初の呼び出しをすぐに実行します。 dueTime が Infinite の場合、コールバックはそのメソッドを呼び出しません。タイマーは無効になっていますが、Change メソッドを使用して再度有効にすることができます。 period が 0 または Infinite で、 dueTime が Infinite ではない場合、コールバックはそのメソッドを 1 回呼び出します。タイマーの定期的な動作は無効になっていますが、Change メソッドを使用して再度有効にすることができます。 period がゼロ (0) または Infinite で、 dueTime が Infinite ではない場合、コールバックはそのメソッドを 1 回呼び出します。タイマーの定期的な動作は無効になっていますが、Change メソッドを使用して再度有効にすることができます。
タイマーを作成した後にその期間と期限を変更したい場合は、Timer の Change メソッドを呼び出して変更できます。
public bool Change(
int dueTime、
整数期間
);//明らかに、変更された 2 つのパラメータは Timer の 2 つのパラメータに対応します。
public static int Main(string[] args)
{
Console.WriteLine (「期間は 1000」);
タイマー tm=新しいタイマー (新しいタイマーコールバック (タイマーコール),3,1000,1000);
スレッド.スリープ (2000);
Console.WriteLine (「期間は 500」);
tm.変更 (0,800);
スレッド.スリープ (3000);
0を返します。
}
public static void TimerCall(オブジェクトb)
{
Console.WriteLine ("タイマーコールバック; b は {0}",b);
}
その操作の結果は次のようになります。
期間は1000です
タイマーコールバック;b は 3
タイマーコールバック;b は 3
期間は500です
タイマーコールバック;b は 3
タイマーコールバック;b は 3
タイマーコールバック;b は 3
タイマーコールバック;b は 3
要約する
上記の簡単な説明から、それぞれが使用される場面がわかります。 Thread は、スレッドの複雑な制御が必要な場面に適しており、ThreadPool は、複数のスレッド (頻繁にブロックされるスレッドなど) を必要とする短いタスクに適しています。 ); タイマーは、定期的に呼び出す必要があるメソッドに適しています。それぞれの用途の特徴を理解していれば、適切な方法をうまく選択できます。