8

TPL に重大なバグを発見したと思います。私はわかりません。私は頭をかきむしるのに多くの時間を費やしましたが、その行動が理解できません。誰でも助けることができますか?

私のシナリオは次のとおりです。

  1. 簡単なことをするタスクを作成します。例外等はありません。
  2. ExecuteSynchronously を設定して継続を登録します。同じスレッド上にある必要があります。
  3. デフォルトのタスクスケジューラ (ThreadPool) でタスクを開始します。開始スレッドは続行し、それを待ちます。
  4. タスクが開始されます。パスします。
  5. 継続はタスクと同じスレッドで開始され (前のタスクが完了します!)、無限ループに入ります。
  6. 待機中のスレッドでは何も起こりません。それ以上行きたくない。待って立ち往生。デバッガーでチェックインしました。タスクは RunToCompletion です。

これが私のコードです。どんな助けにも感謝します!

// note: using new Task() and then Start() to avoid race condition dangerous
// with TaskContinuationOptions.ExecuteSynchronously flag set on continuation.

var task = new Task(() => { /* just return */ });
task.ContinueWith(
   _task => { while (true) { } /* never return */ },
   TaskContinuationOptions.ExecuteSynchronously);

task.Start(TaskScheduler.Default);
task.Wait(); // a thread hangs here forever even when EnterEndlessLoop is already called. 
4

3 に答える 3

5

あなたに代わって接続のバグを報告しました - 大丈夫だといいのですが:)

https://connect.microsoft.com/VisualStudio/feedback/details/744326/tpl-wait-call-on-task-doesnt-return-until-all-continuations-scheduled-with-executesynchronously-also-complete

私はそれがバグであることに同意します。「無限」の待機なしで問題を示すコード スニペットを投稿したかっただけです。バグは、ExecuteSynchronously が、最初のタスクの Wait 呼び出しが、ExecuteSynchronously の継続も完了するまで返されないことを意味することです。

以下のスニペットを実行すると、17 秒待機することが示されます (したがって、最初のスニペットだけでなく、3 つすべてを完了する必要がありました)。3 番目のタスクが最初のタスクまたは 2 番目のタスクからスケジュールされているかどうかは同じです (したがって、この ExecuteSynchronously は、そのようにスケジュールされたタスクのツリーを続行します)。

void Main()
{
    var task = new Task(() => Thread.Sleep(2 * 1000));
    var secondTask = task.ContinueWith(
        _ => Thread.Sleep(5 * 1000),
        TaskContinuationOptions.ExecuteSynchronously);
    var thirdTask = secondTask.ContinueWith(
        _ => Thread.Sleep(10 * 1000),
        TaskContinuationOptions.ExecuteSynchronously);

    var stopwatch = Stopwatch.StartNew();
    task.Start(TaskScheduler.Default);
    task.Wait();
    Console.WriteLine ("Wait returned after {0} seconds", stopwatch.ElapsedMilliseconds / 1000.0);
}

それが意図的である可能性があると思わせる唯一のこと(したがって、コードのバグというよりもドキュメントのバグ) は、このブログ投稿の Stephen のコメントです。

ExecuteSynchronously は、継続した前件タスクを完了したのと同じスレッドで継続タスクを実行するための最適化の要求であり、実際には前件の最終状態への遷移の一部として継続を実行します。

于 2012-05-27T17:23:13.947 に答える
3

この動作は、タスクのインライン化を考慮すると理にかなっています。タスクが実行を開始する前にを呼び出すとTask.Wait、スケジューラはタスクをインライン化しようとします。つまり、 を呼び出したのと同じスレッドで実行しますTask.Wait。これは理にかなっています。タスクを実行するためにスレッドを再利用できるのに、なぜタスクを待っているスレッドを無駄にするのでしょうか?

現在、ExecuteSynchronouslyが指定されている場合、スケジューラは、先行タスクと同じスレッド (インライン化の場合は元の呼び出しスレッド) で継続を実行するように指示されます。

インライン化が行われない場合、期待していた動作が行われることに注意してください。あなたがしなければならないのは、インライン化を禁止することだけです。それは簡単です - 待機のタイムアウトを指定するか、キャンセルトークンを渡します。

task.Wait(new CancellationTokenSource().Token); //This won't wait for the continuation

最後に、インライン化は保証されていないことに注意してください。私のマシンでは、呼び出しの前にタスクが既に開始されていたため、それは起こりませんでした。Waitそのため、私のWait呼び出しはブロックされませんでした。再現可能なブロックが必要な場合は、 を呼び出しますTask.RunSynchronously

于 2012-05-28T13:48:56.897 に答える
0

したがって、それは実際にはバグであることがわかります。誰もが同意すると思います。ただし、これがこのように動作することを意図していた場合、API とドキュメントは非常に誤解を招くようです。

私が使用した回避策は、単純に ManualResetEventSlim を使用することです。

var eventSlim = new ManualResetEventSlim(false);
var task = new Task(() => { eventSlim.Set(); });
task.ContinueWith(
   _task => { while (true) { } /* never return */ },
   TaskContinuationOptions.ExecuteSynchronously);

task.Start(TaskScheduler.Default);
eventSlim.Wait();

ご覧いただきありがとうございます!そして、すべてのコメント。

よろしく。

于 2012-05-27T18:45:32.883 に答える