並列プログラミングは初めてです。 .NETでは、Task
とThread
の2つのクラスを使用できます。
だから、私の質問は:
Thread
とTask
を使用したほうがよいですか?Thread
は低レベルの概念です。直接スレッドを起動しているのであれば、スレッドプールなどで実行するのではなく、knowという別のスレッドになります。
Task
は「どこかでコードを実行する場所」を抽象化したものにとどまりません - 実際には「将来の結果を約束するもの」にすぎません。だからいくつかの異なる例として:
Task.Delay
は実際のCPU時間を必要としません。将来タイマーをオフにするようなものです。WebClient.DownloadStringTaskAsync
によって返されたタスクは、ローカルではそれほどCPU時間がかかりません。それは、ネットワークの待ち時間や(Webサーバーでの)遠隔作業に時間を費やす可能性が高い結果を表しています。Task.Run()
really によって返されるタスクはで、「このコードを別に実行してほしい」と言っています。そのコードが実行される正確なスレッドは、いくつかの要因によって異なります。Task<T>
抽象化は、C#5の非同期サポートにとって極めて重要です。
一般的に、可能な限り高いレベルの抽象化を使用することをお勧めします。最新のC#コードでは、自分自身のスレッドを明示的に開始する必要はほとんどないはずです。
スレッド
Threadは、独自のスタックとカーネルリソースを持つ、実際のOSレベルのスレッドを表します。 (技術的には、CLRの実装では代わりにファイバを使用できますが、既存のCLRではこれを実行できません)Threadでは最高レベルの制御が可能です。スレッドをAbort()、Suspend()、またはResume()して(これは非常に悪い考えですが)、その状態を観察でき、スタックサイズ、アパートメントの状態、カルチャなどのスレッドレベルのプロパティを設定できます。
Threadの問題は、OSのスレッドが高価であるということです。あなたが持っているそれぞれのスレッドはそのスタックのために些細ではない量のメモリを消費し、そしてスレッド間のプロセッサコンテキストスイッチとして追加のCPUオーバーヘッドを追加します。代わりに、作業が可能になったときに小さなスレッドプールでコードを実行することをお勧めします。
代替スレッドがない場合があります。名前(デバッグ用)またはアパートメントの状態(UIを表示するため)を指定する必要がある場合は、独自のスレッドを作成する必要があります(複数のUIスレッドを持つことは一般に悪い考えです)。また、単一のスレッドが所有し、そのスレッドでしか使用できないオブジェクトを維持したい場合は、そのスレッド用のThreadインスタンスを明示的に作成する方がはるかに簡単であるため、使用しようとするコードが実行中かどうかを簡単に確認できます。正しいスレッドで。
ThreadPool
ThreadPoolは、CLRによって管理されているスレッドのプールのラッパーです。 ThreadPoolを使用すると、まったく制御できません。ある時点で作業を実行依頼したり、プールのサイズを制御したりできますが、それ以外は設定できません。あなたがそれに提出した作業をプールがいつ実行し始めるのかさえわかりません。
ThreadPoolを使用すると、多すぎるスレッドを作成することによるオーバーヘッドを回避できます。ただし、長時間実行するタスクをスレッドプールに送信しすぎると、タスクがいっぱいになる可能性があり、後で送信した作業によって、以前の長時間実行される項目が終了するのを待つことになります。さらに、ThreadPoolには、作業項目がいつ完了したかを調べる方法(Thread.Join()とは異なります)も、結果を取得する方法もありません。したがって、ThreadPoolは、呼び出し側が結果を必要としない短い操作に最適です。
タスク
最後に、Task Parallel LibraryのTaskクラスは両方の長所を提供します。 ThreadPoolと同様に、タスクは独自のOSスレッドを作成しません。代わりに、タスクはTaskSchedulerによって実行されます。デフォルトのスケジューラは単にThreadPool上で実行されます。
ThreadPoolとは異なり、Taskでは、いつ終了したかを調べたり、(一般的なTaskを介して)結果を返したりすることもできます。既存のタスクに対してContinueWith()を呼び出して、タスクが終了したときに他のコードを実行するようにすることができます(既に終了している場合は、直ちにコールバックを実行します)。タスクが一般的なものである場合、ContinueWith()はあなたにタスクの結果を渡し、あなたがそれを使用するより多くのコードを実行することを可能にします。
また、Wait()を呼び出して(または一般的なタスクの場合はResultプロパティを取得して)、タスクの終了を同期的に待つこともできます。 Thread.Join()のように、これはタスクが終了するまで呼び出しスレッドをブロックします。タスクを同期的に待機するのは、通常悪い考えです。これは、呼び出し側スレッドが他の作業を実行するのを防ぎ、またタスクが現在のスレッドを(非同期でさえも)待機してしまうと、デッドロックを引き起こす可能性もあります。
タスクは依然としてThreadPool上で実行されるので、スレッドプールをいっぱいにして新しい作業をブロックする可能性があるため、長時間実行される操作には使用しないでください。代わりに、TaskはLongRunningオプションを提供します。これはTaskSchedulerにThreadPoolで実行するのではなく、新しいスレッドをスピンアップするように指示します。
Parallel.For *()メソッド、PLINQ、C#5を含むBCLの最新の非同期メソッドなど、すべての最新の高レベル並行処理APIは、すべてTask上に構築されています。
結論
肝心なことに、タスクはほとんど常に最良の選択肢です。それははるかに強力なAPIを提供し、OSスレッドを無駄にすることを避けます。
最新のコードで明示的に独自のスレッドを作成する唯一の理由は、スレッドごとのオプションを設定すること、または独自のIDを維持する必要がある永続的なスレッドを維持することです。
通常、あなたはを聞くタスクはスレッドよりも高いレベルの概念です...そしてそれはこのフレーズが意味するものです:
Abort/ThreadAbortedExceptionを使用することはできません。定期的にtoken.IsCancellationRequested
フラグをテストする「ビジネスコード」でキャンセルイベントをサポートする必要があります(たとえば、dbへの長いまたはタイムアウトのない接続を避けてください。同様の理由により、Thread.Sleep(delay)
呼び出しをTask.Delay(delay, token)
呼び出しに置き換える必要があります。
タスクにはスレッドのSuspend
およびResume
メソッド機能はありません。 タスクのインスタンスも再利用できません。
ただし、次の2つの新しいツールがあります。
a)continuations
// continuation with ContinueWhenAll - execute the delegate, when ALL
// tasks[] had been finished; other option is ContinueWhenAny
Task.Factory.ContinueWhenAll(
tasks,
() => {
int answer = tasks[0].Result + tasks[1].Result;
Console.WriteLine("The answer is {0}", answer);
}
);
b)入れ子/子タスク
//StartNew - starts task immediately, parent ends whith child
var parent = Task.Factory.StartNew
(() => {
var child = Task.Factory.StartNew(() =>
{
//...
});
},
TaskCreationOptions.AttachedToParent
);
そのため、システムスレッドはタスクから完全に隠されますが、タスクのコードは具体的なシステムスレッドで実行されます。 システムスレッドはタスクのリソースであり、もちろんタスクの並列実行のフードの下にスレッドプールがまだあります。スレッドが実行する新しいタスクを取得する方法はさまざまです。別の共有リソースTaskSchedulerはそれを気にします。 TaskSchedulerが解決するいくつかの問題1)切り替えコストを最小化する同じスレッドでタスクとそのcontinnuationを実行することを好む-別名インライン実行)2)開始された順序でタスクを実行することを好む-別名PreferFairness3)「タスクアクティビティの事前知識に応じて、非アクティブなスレッド間でタスクをより効果的に分散する"-別名ワークスチール。重要:一般に、「非同期」は「並列」とは異なります。 TaskSchedulerオプションを使用して、非同期タスクが1つのスレッドで同期的に実行されるように設定できます。並列コード実行を表現するには、(タスクよりも)高い抽象化を使用できます: Parallel.ForEach
、 PLINQ
、 Dataflow
。
タスクはC#async/await機能(別名Promise Model)と統合されています。たとえば、そこにrequestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
client.RequestAsync
を実行してもUIスレッドはブロックされません。重要:内部ではClicked
デリゲート呼び出しは完全に規則的です(すべてのスレッドはコンパイラーによって行われます)。
それは選択をするのに十分です。ハングする傾向のあるレガシーAPI(タイムアウトレス接続など)の呼び出しのキャンセル機能をサポートする必要があり、この場合Thread.Abort()をサポートする場合、またはマルチスレッドバックグラウンド計算を作成し、サスペンド/再開を使用してスレッド間の切り替えを最適化する場合、それは並列実行を手動で管理することを意味します-スレッドにとどまります。それ以外の場合、タスクのグループを簡単に操作でき、言語に統合され、開発者の生産性が向上するため、タスクに移動します- タスク並列ライブラリ(TPL) .
Thread
クラスは、Windowsで thread を作成および操作するために使用されます。
Task
は非同期操作を表し、 Task Parallel Library の一部であり、タスクを非同期的および並列的に実行するためのAPIのセットです。
昔(つまりTPL以前)は、Thread
クラスを使用するのがバックグラウンドまたは並列でコードを実行するための標準的な方法の1つでした(より良い代替方法は ThreadPool
を使用することでした)ただし、これは面倒で、いくつかの欠点がありました。特に、バックグラウンドでタスクを実行するためのまったく新しいスレッドを作成することによるパフォーマンスのオーバーヘッドが問題でした。
タスクとTPLを使用することは、システムリソースをはるかに効率的に使用することを可能にする抽象化を提供するため、今日では90%というはるかに優れたソリューションです。コードを実行しているスレッドを明示的に制御したいシナリオがいくつかあると思いますが、一般的に言えば、非同期に何かを実行したい場合は、最初の呼び出しポートをTPLにする必要があります。