24

私はテストしていますasyncが、理解できないこの状況を見つけました。

var watch = Stopwatch.StartNew();

var t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

var t2 = Task.Factory.StartNew(() =>
{
    Task.Delay(1000);

    return 1; 
});

await Task.WhenAll(t1, t2);

var result = watch.ElapsedMilliseconds;

結果が常に0になる理由を理解したいと思います。1000、2000、または2つのタスクの合計が3000ではないのはなぜですか?Task.WhenAllタスクの完了を待たないのはなぜですか?

4

1 に答える 1

47

さて、2つ目は簡単なものなので、それを処理しましょう。

2番目のタスクではt2、の結果に対して何もしませんTask.Delay(1000)。あなたはawaitそれをしません、あなたはそれをしませんWait、等。方法がそうでないことを考えると、async私はあなたがそれがブロッキング待機であることを意図したと思いました。Wait()これを行うには、呼び出しの最後に追加しDelayてブロッキング待機にするか、またはを使用しますThread.Sleep()


最初のタスクでは、を使用しているという事実に噛まれていますvar。使用しない場合に何が起こっているかがより明確になりますvar

Task<Task<int>> t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

のだけでintなく、のタスクのタスクを返します。内側のタスクの開始が完了するとすぐに、外側のタスクが「完了」します。使用するときは、外側のタスクがいつ終了するかを気にせず、内側のタスクがいつ終了するかを気にします。これを処理する方法はいくつかあります。1つはを使用することです。TaskintWhenAllUnwrap

Task<int> t1 = Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
}).Unwrap();

これで、期待どおりTask<int>WhenAllなり、少なくとも2000ミリ秒かかります。await同じことを行うために別の呼び出しを追加することもできます。

Task<int> t1 = await Task.Factory.StartNew(async () =>
{
    await Task.Delay(2000);

    return 2;
});

コメントでsvickが述べたように、別のオプションは、Task.Runの代わりに使用することですStartNewTask.Runを取り、aFunc<Task<T>>を返し、Task<T>それらを自動的にアンラップするメソッド用の特別なオーバーロードのセットがあります。

Task<int> t1 = Task.Run(async () =>
{
    await Task.Delay(2000);

    return 2;
});

このため、Task.Run非同期ラムダを作成する場合は、この問題を「処理」するため、デフォルトのオプションとして使用することをお勧めします。ただし、を使用できない複雑なケースでは注意するのが最善ですTask.Run


最後に、あなたがしなかったオプションに行き着きます。これは、この場合、おそらく実際に行うべきことです。すでにタスクを返しているのでTask.Delay、そもそもタスクを入れる必要はありませんStartNew。ネストされたタスクを作成して使用Unwrapするのではなく、そもそもそれをラップすることはできません。

var t3 = Task.Delay(3000);
await Task.WhenAll(t1, t2, t3);

あなたが実際に一定の時間だけ待ちたいのなら、それはあなたがしなければならないことです。

于 2012-12-28T15:32:54.340 に答える