Tôi chỉ thực hiện một quan sát tò mò về Task.WhenAll
phương pháp, khi chạy trên .NET Core 3.0. Tôi đã chuyển một Task.Delay
tác vụ đơn giản thành một đối số duy nhất Task.WhenAll
và tôi dự kiến rằng tác vụ được bao bọc sẽ hành xử giống hệt với tác vụ ban đầu. Nhưng đây không phải là trường hợp. Các phần tiếp theo của tác vụ gốc được thực thi không đồng bộ (điều này là mong muốn) và phần tiếp theo của nhiều Task.WhenAll(task)
hàm bao được thực hiện đồng bộ lần lượt từng phần tử (điều không mong muốn).
Dưới đây là bản demo của hành vi này. Bốn nhiệm vụ công nhân đang chờ cùng một Task.Delay
nhiệm vụ để hoàn thành, và sau đó tiếp tục với một tính toán nặng nề (mô phỏng bởi 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);
Đây là đầu ra. Bốn phần tiếp theo đang chạy như mong đợi trong các luồng khác nhau (song song).
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
Bây giờ nếu tôi nhận xét dòng await task
và bỏ ghi chú dòng sau await Task.WhenAll(task)
, đầu ra hoàn toàn khác. Tất cả các phần tiếp theo đang chạy trong cùng một luồng, vì vậy các tính toán không được song song. Mỗi tính toán được bắt đầu sau khi hoàn thành trước đó:
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
Đáng ngạc nhiên là điều này chỉ xảy ra khi mỗi công nhân chờ đợi một trình bao bọc khác nhau. Nếu tôi xác định trả trước bao bọc:
var task = Task.WhenAll(Task.Delay(500));
... Và sau đó await
là cùng một nhiệm vụ bên trong tất cả các công nhân, hành vi này giống hệt với trường hợp đầu tiên (tiếp tục không đồng bộ).
Câu hỏi của tôi là: tại sao điều này xảy ra? Điều gì gây ra sự tiếp tục của các trình bao bọc khác nhau của cùng một tác vụ để thực thi trong cùng một luồng, một cách đồng bộ?
Lưu ý: gói một nhiệm vụ Task.WhenAny
thay vì Task.WhenAll
kết quả trong cùng một hành vi lạ.
Một quan sát khác: Tôi dự kiến rằng việc bọc trình bao bọc bên trong Task.Run
sẽ làm cho các phần tiếp theo không đồng bộ. Nhưng nó không xảy ra. Các phần tiếp theo của dòng bên dưới vẫn được thực hiện trong cùng một luồng (đồng bộ).
await Task.Run(async () => await Task.WhenAll(task));
Làm rõ: Sự khác biệt ở trên đã được quan sát thấy trong một ứng dụng Console chạy trên nền tảng .NET Core 3.0. Trên .NET Framework 4.8, không có sự khác biệt giữa việc chờ đợi tác vụ gốc hoặc trình bao bọc tác vụ. Trong cả hai trường hợp, các phần tiếp theo được thực hiện đồng bộ, trong cùng một luồng.
Task.WhenAll
Task.Delay
từ ban đầu 100
sang 1000
để nó không được hoàn thành khi await
ed.
await Task.WhenAll(new[] { task });
?