web-dev-qa-db-ja.com

Task.Delay()。Wait()で何が起こっているのですか?

Task.Delay().Wait()4倍以上の時間を必要とする理由が混乱しています。その後、Thread.Sleep()

例えば。 task-が実行されていたスレッド9のみと実行2193ms?スレッド全体がブロックされているので、同期待機はタスクでは悪いことを知っています。テスト用です。

コンソールアプリケーションでの簡単なテスト:

_bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(() =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");                     
            //Thread.Sleep(wait);
            Task.Delay(wait).Wait();
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
            ;
        });
    }
}
Console.ReadKey();
return;
_

with Task.Delay().Wait()
task-03 ThrID:05、Wait = 300ms、START:184ms
task-04 ThrID:07、Wait = 100ms、START:184ms
task-00 ThrID:09、Wait = 100ms、START:0ms
task-06 ThrID:04、Wait = 100ms、START:185ms
task-01 ThrID:08、待機= 300ミリ秒、開始:183ミリ秒
task-05 ThrID:03、Wait = 300ms、START:185ms
task-02 ThrID:06、Wait = 100ms、START:184ms
task-07 ThrID:10、待機= 300ミリ秒、開始:209ミリ秒
task-07 ThrID:10、Wait = 300ms、END:1189ms
task-08 ThrID:12、Wait = 100ms、START:226ms
task-09 ThrID:10、Wait = 300ms、START:226ms
task-09 ThrID:10、Wait = 300ms、END:2192ms
task-06 ThrID:04、Wait = 100ms、END:2193ms
task-08 ThrID:12、Wait = 100ms、END:2194ms
task-05 ThrID:03、Wait = 300ms、END:2193ms
task-03 ThrID:05、Wait = 300ms、END:2193ms
task-00 ThrID:09、Wait = 100ms、END:2193ms
task-02 ThrID:06、Wait = 100ms、END:2193ms
task-04 ThrID:07、Wait = 100ms、END:2193ms
task-01 ThrID:08、Wait = 300ms、END:2193ms

with Thread.Sleep()
task-00 ThrID:03、Wait = 100ms、START:0ms
task-03 ThrID:09、待機= 300ミリ秒、開始:179ミリ秒
task-02 ThrID:06、Wait = 100ms、START:178ms
task-04 ThrID:08、待機= 100ミリ秒、開始:179ミリ秒
task-05 ThrID:04、Wait = 300ms、START:179ms
task-06 ThrID:07、Wait = 100ms、START:184ms
task-01 ThrID:05、Wait = 300ms、START:178ms
task-07 ThrID:10、Wait = 300ms、START:184ms
task-00 ThrID:03、Wait = 100ms、END:284ms
task-08 ThrID:03、Wait = 100ms、START:184ms
task-02 ThrID:06、Wait = 100ms、END:285ms
task-09 ThrID:06、Wait = 300ms、START:184ms
task-04 ThrID:08、Wait = 100ms、END:286ms
task-06 ThrID:07、Wait = 100ms、END:293ms
task-08 ThrID:03、Wait = 100ms、END:385ms
task-03 ThrID:09、Wait = 300ms、END:485ms
task-05 ThrID:04、Wait = 300ms、END:486ms
task-01 ThrID:05、Wait = 300ms、END:493ms
task-07 ThrID:10、Wait = 300ms、END:494ms
task-09 ThrID:06、Wait = 300ms、END:586ms

編集:
asyncラムダとawaitを使用すると、Task.Delay()Thread.Sleep()と同じくらい高速で、高速になる場合もあります(511ms)。
編集2:
With ThreadPool.SetMinThreads(16, 16);Task.Delay().Wait()は、ループの10回の反復で_Thread.Sleep_と同じくらい速く機能します。反復回数が増えると、再び遅くなります。興味深いのは、調整せずに_Thread.Sleep_の反復回数をに増やしても、さらに高速であり、次に1Task.Delay().Wait()
編集3:
オーバーロードTask.Delay(wait).Wait(wait)Thread.Sleep()と同じ速さで動作します

11
Rekshino

投稿されたスニペットを少し書き直して結果の順序を整えました。新しいラップトップはコアが多すぎて、既存の乱雑な出力を十分に解釈できません。各タスクの開始時間と終了時間を記録し、すべて完了した後に表示します。そして、タスクの実際の開始時間を記録します。私は得ました:

0: 68 - 5031
1: 69 - 5031
2: 68 - 5031
3: 69 - 5031
4: 69 - 1032
5: 68 - 5031
6: 68 - 5031
7: 69 - 5031
8: 1033 - 5031
9: 1033 - 2032
10: 2032 - 5031
11: 2032 - 3030
12: 3030 - 5031
13: 3030 - 4029
14: 4030 - 5031
15: 4030 - 5031

ああ、それは突然大いに理にかなっています。スレッドプールスレッドを処理するときに常に監視するパターン。 1秒間に重要な何かが発生し、2つのtpスレッドが実行を開始し、そのうちのいくつかが完了することに注意してください。

これは this Q + A に似たデッドロックシナリオですが、それ以外はそのユーザーのコードの悲惨な結果はありません。 .NETFrameworkコードに埋め込まれているため、原因を特定することはほとんど不可能です。それを理解するには、Task.Delay()の実装方法を確認する必要があります。

関連コード ここにあります 、System.Threading.Timerを使用して遅延を実装する方法に注意してください。そのタイマーについての詳細は、コールバックがスレッドプールで実行されることです。これは、Task.Delay()が「使用しないものに対して料金を支払わない」という約束を実装できる基本的なメカニズムです。

スレッドプールの実行要求でスレッドプールがビジー状態になると、これには時間がかかる場合があります。タイマーが遅いのではなく、問題は、コールバックメソッドがすぐに開始されないことです。このプログラムの問題であるTask.Run()は、同時に実行できるよりも多くのリクエストを追加しました。デッドロックが発生するのは、Task.Run()によって開始されたtp-threadが、タイマーコールバックが実行されるまでWait()呼び出しを完了できないためです。

Main()の先頭に次のコードを追加することで、プログラムを永久にハングさせるハードデッドロックにすることができます。

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

しかし、通常のmax-threadsははるかに高くなります。この種のデッドロックを解決するためにスレッドプールマネージャーが利用するもの。 1秒に1回、既存のスレッドが完了しないときに、「理想的な」数よりも2つ多いスレッドを実行できます。これが出力に表示されるものです。しかし、それは一度に2つだけであり、Wait()コールでブロックされる8つのビジーなスレッドに多くのへこみを入れるには不十分です。

Thread.Sleep()呼び出しにはこの問題はありません。NETFrameworkコードやスレッドプールの完了に依存しません。それを処理するのはOSスレッドスケジューラーであり、常にクロック割り込みによって実行されます。したがって、新しいtpスレッドが毎秒ではなく100または300ミリ秒ごとに実行を開始できるようにします。

このようなデッドロックトラップを回避するために具体的なアドバイスをするのは困難です。一般的なアドバイス以外に、常にワーカースレッドをブロックしないでください。

7
Hans Passant

Thread.Sleep()Task.Delay()も、内部が正しいことを保証しません。

Thread.Sleep()作業Task.Delay()は非常に異なります。 Thread.Sleep()は現在のスレッドをブロックし、コードが実行されないようにします。 Task.Delay()は、時間が経過したときにティックするタイマーを作成し、スレッドプールでの実行に割り当てます。

Task.Run()を使用してコードを実行します。これにより、タスクが作成され、スレッドプールにエンキューされます。 Task.Delay()を使用すると、現在のスレッドがスレッドプールで解放され、別のタスクの処理を開始できます。このようにして、複数のタスクがより速く開始し、すべての起動時間を記録します。次に、遅延タイマーが作動し始めると、プールも使い果たされ、一部のタスクは開始後よりもかなり時間がかかります。だから長時間録音します。

Thread.Sleep()を使用すると、プールの現在のスレッドがブロックされ、それ以上のタスクを処理できなくなります。スレッドプールはすぐには増加しないため、新しいタスクはそのまま待機します。したがって、すべてのタスクがほぼ同時に実行され、より高速に見えます。

編集:Task.Wait()を使用します。あなたの場合、 Task.Wait() は、同じスレッドで実行をインライン化しようとします。同時に、Task.Delay()は、スレッドプールで実行されるタイマーに依存します。 Task.Wait()を呼び出すことで、プールからワーカースレッドをブロックし、次に、同じワーカーメソッドの操作を完了するためにプールで利用可能なスレッドを要求します。 awaitDelay()を使用する場合、そのようなインライン化は不要であり、タイマーイベントを処理するためにワーカースレッドをすぐに使用できます。 Thread.Sleep、ワーカーメソッドを完了するタイマーがありません。

これが遅延の劇的な違いを引き起こしているものだと私は信じています。

5
Nick

あなたの問題は、asyncawaitを使用せずに非同期コードと同期コードを混在させていることです。同期呼び出し_.Wait_を使用しないでください。これはスレッドをブロックするため、非同期コードTask.Delay()は正しく機能しません。

非同期コードは、そのように機能するように設計されていないため、同期的に呼び出された場合、適切に機能しないことがよくあります。同期的に実行すると、幸運にも非同期コードが動作するように見えることがあります。しかし、そのライブラリの外部ライブラリ作成者を使用している場合は、コードが壊れるようにコードを変更できます。非同期コードは、非同期で完全にダウンする必要があります。

非同期コードは通常、同期コードよりも低速です。しかし、利点は、コードが非同期で実行されることです。たとえば、コードがファイルのロードを待機している場合、そのファイルのロード中に他のコードが同じCPUコアで実行される可能性があります。

コードは以下のようになりますが、asyncを使用すると、ManagedThreadIdが同じままであるかどうか確信が持てません。コードを実行しているスレッドは実行中に変更される可能性があるためです。いずれにせよ、非同期コードを使用する場合は、ManagedThreadIdプロパティまたは_[ThreadStatic]_属性を使用しないでください。

非同期/待機-非同期プログラミングのベストプラクティス

_bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(async () =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");
            await Task.Delay(wait);
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
        });
    }
}
Console.ReadKey();
return;
_
0
Wanton