Toàn bộ ý tưởng đằng sau Parallel.ForEach()
là bạn có một tập hợp các luồng và mỗi luồng xử lý một phần của bộ sưu tập. Như bạn nhận thấy, điều này không hoạt động với async
- await
, nơi bạn muốn giải phóng chuỗi trong suốt thời gian của cuộc gọi không đồng bộ.
Bạn có thể khắc phục lỗi đó bằng cách chặn các ForEach()
luồng, nhưng điều đó đánh bại toàn bộ quan điểm của async
- await
.
Những gì bạn có thể làm là sử dụng TPL Dataflow thay vì Parallel.ForEach()
, hỗ trợ tốt cho sự không đồng bộ Task
.
Cụ thể, mã của bạn có thể được viết bằng cách sử dụng TransformBlock
biến đổi từng id thành Customer
sử dụng async
lambda. Khối này có thể được cấu hình để thực hiện song song. Bạn sẽ liên kết khối đó với một khối ActionBlock
ghi mỗi Customer
bảng điều khiển. Sau khi bạn thiết lập mạng chặn, bạn có thể Post()
mỗi id thành TransformBlock
.
Trong mã:
var ids = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
var getCustomerBlock = new TransformBlock<string, Customer>(
async i =>
{
ICustomerRepo repo = new CustomerRepo();
return await repo.GetCustomer(i);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
});
var writeCustomerBlock = new ActionBlock<Customer>(c => Console.WriteLine(c.ID));
getCustomerBlock.LinkTo(
writeCustomerBlock, new DataflowLinkOptions
{
PropagateCompletion = true
});
foreach (var id in ids)
getCustomerBlock.Post(id);
getCustomerBlock.Complete();
writeCustomerBlock.Completion.Wait();
Mặc dù bạn có thể muốn hạn chế sự song song của TransformBlock
một số hằng số nhỏ. Ngoài ra, bạn có thể giới hạn dung lượng của TransformBlock
và thêm các mục vào nó không đồng bộ bằng cách sử dụng SendAsync()
, ví dụ nếu bộ sưu tập quá lớn.
Như một lợi ích bổ sung khi so sánh với mã của bạn (nếu nó hoạt động) là việc viết sẽ bắt đầu ngay khi một mục duy nhất kết thúc và không đợi cho đến khi tất cả quá trình xử lý kết thúc.