この質問について、 PFXチームのメンバーであるStephenToubにメールを送りました。彼は非常に迅速に、詳細を含めて私に戻ってきたので、ここに彼のテキストをコピーして貼り付けます。引用されたテキストを大量に読むと、バニラの白地に黒よりも快適でなくなるため、すべてを引用していませんが、実際には、これはスティーブンです-私はこれほど多くのものを知りません:)私は作りましたこの回答コミュニティウィキは、以下のすべての長所が実際には私のコンテンツではないことを反映しています。
Wait()
完了したタスクを呼び出す場合、ブロックは発生しません(タスクが、以外のTaskStatusで完了した場合は例外をスローするか、それ以外の場合はnopRanToCompletion
として返されます)。すでに実行されているタスクを呼び出す場合、それが合理的に実行できることは他にないため、ブロックする必要があります(ブロックと言うときは、通常、両方の混合を実行するため、真のカーネルベースの待機と回転の両方を含めます)。同様に、またはステータスのタスクを呼び出すと、タスクが完了するまでブロックされます。それらのどれも議論されている興味深いケースではありません。 Wait()
Wait()
Created
WaitingForActivation
Wait()
興味深いケースは、その状態でタスクを呼び出す場合ですWaitingToRun
。つまり、以前はTaskSchedulerのキューに入れられていましたが、TaskSchedulerはまだ実際にタスクのデリゲートを実行していません。その場合、への呼び出しは、スケジューラーのメソッドWait
への呼び出しを介して、現在のスレッドでタスクを実行してもよいかどうかをスケジューラーに尋ねます。TryExecuteTaskInline
これはインライン化と呼ばれます。スケジューラーは、への呼び出しを介してタスクをインライン化するか、タスクをbase.TryExecuteTask
実行していないことを示すために「false」を返すかを選択できます(多くの場合、これは...のようなロジックで実行されます。
return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
ブール値を返す理由TryExecuteTask
は、同期を処理して、特定のタスクが1回だけ実行されるようにするためです)。したがって、スケジューラーがの間にタスクのインライン化を完全に禁止したい場合は、次のWait
ように実装できますreturn false;
。スケジューラーが可能な限り常にインライン化を許可したい場合は、次のように実装できます。
return TryExecuteTask(task);
現在の実装(.NET4と.NET4.5の両方で、個人的にこれが変更されることはないと思います)では、ThreadPoolを対象とするデフォルトのスケジューラーにより、現在のスレッドがThreadPoolスレッドであり、そのスレッドが以前にタスクをキューに入れたことがあるもの。
ここでは任意の再入可能性がないことに注意してください。デフォルトのスケジューラーはタスクを待機しているときに任意のスレッドをポンプしません...それはそのタスクをインライン化することだけを許可し、もちろんそのタスクをインライン化することで順番に決定しますやること。Wait
また、特定の条件ではスケジューラーに問い合わせることすらなく、代わりにブロックすることを好むことにも注意してください。たとえば、キャンセル可能なCancellationTokenを渡した場合、または無限でないタイムアウトを渡した場合、タスクの実行をインライン化するのに任意の長い時間がかかる可能性があるため、インライン化を試みません。これはすべてまたはまったくありません。 、そしてそれはキャンセル要求またはタイムアウトを大幅に遅らせることになりかねません。全体として、TPLは、実行しているスレッドを無駄にすることの間で、ここで適切なバランスをとろうとします。Wait
そのスレッドを使いすぎて再利用します。この種のインライン化は、複数のタスクを生成し、それらがすべて完了するのを待つ再帰的な分割統治問題(QuickSortなど)にとって非常に重要です。インライン化せずにそのようなことが行われた場合、プール内のすべてのスレッドと、それが提供したい将来のスレッドを使い果たすため、非常に迅速にデッドロックが発生します。
とは別に、使用されているスケジューラーがQueueTask呼び出しの一部としてタスクを同期的に実行することを選択した場合、 Task.Factory.StartNew呼び出しがタスクを実行することにWait
なる可能性もあります。.NETに組み込まれているスケジューラーはどれもこれを実行しません。個人的には、スケジューラーにとっては悪い設計になると思いますが、理論的には可能です。例:
protected override void QueueTask(Task task, bool wasPreviouslyQueued)
{
return TryExecuteTask(task);
}
そのオーバーロードはTask.Factory.StartNew
、を受け入れません。ターゲットの場合は、TaskScheduler
からのスケジューラを使用します。つまり、この神話のキューに入れられたタスク内から呼び出すと、もキューに入れられ、その結果、呼び出しはタスクを同期的に実行します。これについてまったく心配している場合(たとえば、ライブラリを実装していて、どこから呼び出されるかわからない場合)、明示的に呼び出しに渡して、(常にに行く)を使用できます。または、createdtotargetを使用します。TaskFactory
Task.Factory
TaskScheduler.Current
Task.Factory.StartNew
RunSynchronouslyTaskScheduler
RunSynchronouslyTaskScheduler
StartNew
TaskScheduler.Default
StartNew
Task.Run
TaskScheduler.Default
TaskFactory
TaskScheduler.Default
編集:さて、私は完全に間違っていたようです、そして現在タスクを待っているスレッドはハイジャックされる可能性があります。これが起こっているより簡単な例は次のとおりです。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
static void Main() {
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(Launch).Wait();
}
}
static void Launch()
{
Console.WriteLine("Launch thread: {0}",
Thread.CurrentThread.ManagedThreadId);
Task.Factory.StartNew(Nested).Wait();
}
static void Nested()
{
Console.WriteLine("Nested thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
サンプル出力:
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
ご覧のとおり、待機中のスレッドが新しいタスクを実行するために再利用されることがよくあります。これは、スレッドがロックを取得した場合でも発生する可能性があります。厄介な再入可能。私は適切にショックを受けて心配しています:(