web-dev-qa-db-ja.com

Task.WaitAllがタスクの完了を待機していない

C#での新しいTask非同期プログラミング(今はそれほど新しいものではないかもしれませんが、私にとっては新しいものです)を理解しようとしているときに、問題を見つけて少し理解しました。なぜだかわかりません。

私は問題を修正しましたが、それがなぜ最初から問題だったのかはまだわかりません。誰かが同じ状況に遭遇した場合に備えて、私の経験を共有したいと思いました。

グルが問題の原因を教えてくれれば、それは素晴らしいことであり、たいへんありがたいです。私はいつも知っているのが好きですなぜ何かがうまくいきません!

次のように、テストタスクを作成しました。

_Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
    async (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            await Task.Delay((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();
_

次に、アプリケーションを実行して、何が生成されるかを確認しました。次に出力例をいくつか示します。

6:06:15.5661-3053msの遅延でテストタスクを開始します。
6:06:15.5662-待機が終了しました。
6:06:18.5743-3063.235ms後にテストタスクが終了しました。

ご覧のとおり、Task.WaitAll(tasks);ステートメントはあまり機能しませんでした。実行を続行する前に、合計で1ミリ秒待機しました。

以下で自分の「質問」に回答しましたが、上記で述べたように、これが機能しない理由を説明したいと思うよりも知識のある人がいれば、ぜひ実行してください! (I thinkawait演算子に到達すると、メソッドの「ステップアウト」の実行に何らかの関係がある可能性があります。待機が完了すると、ステップバックします。 。しかし、私はおそらく間違っています)

16
cjk84

Async-awaitでTask.Factory.StartNewを使用しないでください。代わりにTask.Runを使用してください。

非同期メソッドはTask<T>を返します。非同期デリゲートも同様です。 Task.Factory.StartNewTask<T>を返します。その結果はデリゲートパラメータの結果です。したがって、一緒に使用すると、Task<Task<T>>>が返されます。

これがTask<Task<T>>が行うことは、返すタスクがあるまでデリゲートを実行することだけです。つまり、最初の待機に到達したときです。そのタスクが完了するのを待つだけで、メソッド全体を待つのではなく、最初の前の部分だけを待ちます。

これを修正するには、Task.Unwrapを使用して、そのTask<T>を表すTask<Task<T>>>を作成します。

Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);
19
i3arnon

コードの問題は、2つのタスクが実行されていることです。 1つは、Task.Factory.StartNew呼び出しの結果です。これにより、匿名関数がスレッドプールで実行されます。ただし、匿名関数は、非同期操作の完了を表すnestedタスクを生成するようにコンパイルされます。 Task<Task<object>>を待つとき、あなたはouterタスクだけを待っています。内部タスクを待機するには、内部タスクを自動的にアンラップするため、Task.RunではなくTask.Factory.StartNewを使用する必要があります。

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<int> testTask = Task.Run(
    async () =>
    {
        DateTime startTime = DateTime.Now;
        Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay);
        await Task.Delay(delay);
        Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
        return delay;
    });
Task<int>[] tasks = new[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();
10
Douglas

ここに、 Task.WaitAllは、内側のタスクではなく、外側のタスクを待ちます。使用する Task.Runネストされたタスクを持ちません。これがベストプラクティスソリューションです。別の解決策は、内部のタスクも待機することです。例えば:

Task<object> testTask = Task.Factory.StartNew(
    async (obj) =>
        {
            ...
        }
    ).Unwrap();

または:

testTask.Wait();
testTask.Result.Wait();
3
usr

多くのねじ込みと髪の毛を引っ張った後、私は最終的に非同期ラムダを取り除き、_System.Threading.Thread.Sleep_メソッドを使用することを決定しました。

新しいコードは次のようになりました:

_Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<object> testTask = Task.Factory.StartNew<object>(
    (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            System.Threading.Thread.Sleep((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<object>[] tasks = new Task<object>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();
_

注:ラムダメソッドからasyncキーワードを削除したため、タスクタイプは_Task<object>_ではなく単に_Task<Task<object>>_になりました-上記のコードでこの変更を確認できます。

そして出来上がり!出来た! 「待たせてしまいました」が出ました。タスク完了後のメッセージ。

悲しいかな、どこかでTaskコードでSystem.Threading.Thread.Sleep()を使用すべきではないことを読んだことを覚えています。理由が思い出せません。しかし、それは単にテストのためであり、ほとんどのタスクは実際には時間のかかる何かをしているふりをするではなく時間のかかる何かをしているであるため、これは実際には問題。

うまくいけば、これは一部の人々を助けます。私は間違いなく世界で最高のプログラマーではありません(または近い)と私のコードはおそらく素晴らしいではありませんが、誰かを助ける場合は素晴らしい! :)

読んでくれてありがとう。

編集:私の質問に対する他の回答は、なぜ私が問題を抱えていたのかを説明しており、この回答は問題を誤って解決しただけです。 Thread.Sleep(x)への変更効果はありません。答えてくれて助けてくれたみんなありがとう!

1
cjk84