Saya baru saja membuat pengamatan yang aneh tentang Task.WhenAll
metode ini, ketika berjalan di .NET Core 3.0. Saya melewati Task.Delay
tugas sederhana sebagai argumen tunggal Task.WhenAll
, dan saya berharap bahwa tugas yang dibungkus akan berperilaku identik dengan tugas asli. Tapi ini bukan masalahnya. Kelanjutan dari tugas asli dijalankan secara tidak sinkron (yang diinginkan), dan kelanjutan dari beberapa Task.WhenAll(task)
pembungkus dijalankan secara sinkron satu per satu (yang tidak diinginkan).
Ini adalah demo dari perilaku ini. Empat tugas pekerja sedang menunggu Task.Delay
tugas yang sama untuk diselesaikan, dan kemudian melanjutkan dengan perhitungan yang berat (disimulasikan oleh a Thread.Sleep
).
var task = Task.Delay(500);
var workers = Enumerable.Range(1, 4).Select(async x =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");
await task;
//await Task.WhenAll(task);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");
Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
}).ToArray();
Task.WaitAll(workers);
Ini outputnya. Keempat kelanjutan berjalan seperti yang diharapkan di utas yang berbeda (secara paralel).
05:23:25.511 [1] Worker1 before await
05:23:25.542 [1] Worker2 before await
05:23:25.543 [1] Worker3 before await
05:23:25.543 [1] Worker4 before await
05:23:25.610 [4] Worker1 after await
05:23:25.610 [7] Worker2 after await
05:23:25.610 [6] Worker3 after await
05:23:25.610 [5] Worker4 after await
Sekarang jika saya berkomentar baris await task
dan batalkan komentar pada baris berikut await Task.WhenAll(task)
, hasilnya sangat berbeda. Semua lanjutan berjalan di utas yang sama, sehingga perhitungannya tidak diparalelkan. Setiap perhitungan dimulai setelah penyelesaian yang sebelumnya:
05:23:46.550 [1] Worker1 before await
05:23:46.575 [1] Worker2 before await
05:23:46.576 [1] Worker3 before await
05:23:46.576 [1] Worker4 before await
05:23:46.645 [4] Worker1 after await
05:23:47.648 [4] Worker2 after await
05:23:48.650 [4] Worker3 after await
05:23:49.651 [4] Worker4 after await
Anehnya ini hanya terjadi ketika setiap pekerja menunggu pembungkus yang berbeda. Jika saya mendefinisikan pembungkus di muka:
var task = Task.WhenAll(Task.Delay(500));
... dan kemudian await
tugas yang sama di dalam semua pekerja, perilaku identik dengan kasus pertama (kelanjutan asinkron).
Pertanyaan saya adalah: mengapa ini terjadi? Apa yang menyebabkan kelanjutan dari pembungkus berbeda dari tugas yang sama untuk dieksekusi di utas yang sama, secara serempak?
Catatan: menyelesaikan tugas dengan Task.WhenAny
alih - alih Task.WhenAll
menghasilkan perilaku aneh yang sama.
Pengamatan lain: Saya berharap bahwa membungkus bungkus di dalam Task.Run
akan membuat kelanjutan asinkron. Tetapi itu tidak terjadi. Kelanjutan dari baris di bawah ini masih dijalankan di utas yang sama (serempak).
await Task.Run(async () => await Task.WhenAll(task));
Klarifikasi: Perbedaan di atas diamati dalam aplikasi Konsol yang berjalan pada platform .NET Core 3.0. Pada .NET Framework 4.8, tidak ada perbedaan antara menunggu tugas asli atau pembungkus tugas. Dalam kedua kasus, kelanjutan dijalankan secara sinkron, di utas yang sama.
Task.WhenAll
Task.Delay
dari 100
ke 1000
sehingga tidak selesai saat await
ed.
await Task.WhenAll(new[] { task });
?