2014-04-16 19 views
15

Đoạn mã sau biên dịch, nhưng tôi mong đợi nó chờ kết quả nhiệm vụ thay vì cho tôi một số List<Task<T>>.Điều gì thực sự xảy ra khi sử dụng async/await bên trong một câu lệnh LINQ?

var foo = bars.Select(async bar => await Baz(bar)).ToList() 

Như đã chỉ ra here, bạn cần phải sử dụng Task.WhenAll:

var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList(); 
await Task.WhenAll(tasks); 

Nhưng a comment chỉ ra rằng asyncawait bên trong Select() là không cần thiết:

var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList(); 

Một tương tự câu hỏi here nơi ai đó cố gắng sử dụng một phương pháp không đồng bộ bên trong một Where().

Vì vậy, asyncawait bên trong câu lệnh LINQ là cú pháp hợp pháp, nhưng nó không làm gì cả hoặc có sử dụng nhất định không?

Trả lời

21

Tôi khuyên bạn không nên nghĩ điều này là "sử dụng async trong LINQ". Hãy ghi nhớ những gì ở giữa hai người: đại biểu. Một số toán tử LINQ lấy các đại biểu, và async có thể được sử dụng để tạo một đại biểu không đồng bộ.

Vì vậy, khi bạn có một phương pháp không đồng bộ BazAsync mà trả về một Task:

Task BazAsync(TBar bar); 

sau đó mã này kết quả trong một chuỗi các nhiệm vụ:

IEnumerable<Task> tasks = bars.Select(bar => BazAsync(bar)); 

Tương tự, nếu bạn sử dụng asyncawait trong đại biểu, bạn đang tạo một đại biểu không đồng bộ trả về một Task:

IEnumerable<Task> tasks = bars.Select(async bar => await BazAsync(bar)); 

Hai biểu thức LINQ này có chức năng tương đương. Không có sự khác biệt quan trọng.

Cũng giống như các biểu thức LINQ thông thường, các IEnumerable<Task> được đánh giá lười biếng. Chỉ với các phương pháp không đồng bộ như BazAsync, bạn thường làm không phải muốn đánh giá ngẫu nhiên hai lần hoặc bất cứ điều gì tương tự.Vì vậy, khi bạn chiếu một chuỗi các nhiệm vụ, thường là một ý tưởng tốt để lập lại trình tự ngay lập tức. Điều này đòi hỏi BazAsync cho tất cả các yếu tố trong chuỗi nguồn, bắt đầu tất cả các nhiệm vụ đi:

Task[] tasks = bars.Select(bar => BazAsync(bar)).ToArray(); 

Tất nhiên, tất cả chúng tôi đã thực hiện với Select được bắt đầu một hoạt động không đồng bộ cho mỗi yếu tố. Nếu bạn muốn chờ đợi cho họ tất cả để hoàn thành, sau đó sử dụng Task.WhenAll:

await Task.WhenAll(tasks); 

Hầu hết các nhà khai thác LINQ khác không làm việc như sạch sẽ với các đại biểu không đồng bộ. Select là khá đơn giản: bạn chỉ mới bắt đầu một hoạt động không đồng bộ cho mỗi phần tử.

+0

"Không có sự khác biệt quan trọng". - nếu BazAsync ném đồng bộ, sử dụng lambda không đồng bộ, nó sẽ xảy ra trong quá trình liệt kê và sử dụng lambda không đồng bộ, nó sẽ xảy ra trong khi chờ nhiệm vụ. –

+1

@EliArbel: Có. Nhưng một phương pháp không đồng bộ ném đồng bộ là một trường hợp rất khác thường. Các phương thức không đồng bộ nên * chỉ * ném ngoại lệ đồng bộ nếu chúng là [ngoại lệ có đầu xương] (https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/). Vì vậy, tôi không nghĩ rằng một trường hợp sử dụng quan trọng. –

+1

reify - https://en.wikipedia.org/wiki/Reification_(computer_science) – BozoJoe

4

nó có sử dụng một số

chắc. Với async và đang chờ đợi bên trong một câu lệnh LINQ, bạn có thể làm một cái gì đó như thế này:

var tasks = foos.Select(async foo => 
    { 
     var intermediate = await DoSomethingAsync(foo); 
     return await DoSomethingElseAsync(intermediate); 
    }).ToList(); 
await Task.WhenAll(tasks); 

Without async/chờ đợi bên trong một tuyên bố LINQ bạn không chờ đợi bất cứ điều gì bên trong tuyên bố LINQ, vì vậy bạn không thể xử lý kết quả, hoặc chờ đợi cho cái gì khác.

Nếu không có đồng bộ/chờ, trong câu lệnh LINQ, bạn chỉ bắt đầu công việc, nhưng không đợi chúng hoàn thành. Họ vẫn sẽ hoàn thành cuối cùng, nhưng nó sẽ xảy ra lâu sau khi kiểm soát sẽ để lại câu lệnh LINQ, vì vậy bạn chỉ có thể truy cập kết quả của họ sau khi dòng WhenAll sẽ hoàn thành, nhưng không phải bên trong câu lệnh LINQ.

Các vấn đề liên quan