2017-08-28 15 views
9

Tôi có một phương pháp đồng bộ đơn giản, nhìn như thế này:cách khác nhau của không đồng bộ trở về một bộ sưu tập (với C# 7 tính năng)

public IEnumerable<Foo> MyMethod(Source src) 
{ 
    // returns a List of Oof objects from a web service 
    var oofs = src.LoadOofsAsync().Result; 
    foreach(var oof in oofs) 
    { 
     // transforms an Oof object to a Foo object 
     yield return Transform(oof); 
    } 
} 

Kể từ khi phương pháp này là một phần của một ứng dụng web, nó là tốt để sử dụng tất cả tài nguyên hiệu quả nhất có thể. Do đó, tôi muốn thay đổi phương thức thành một số không đồng bộ. Các tùy chọn đơn giản nhất là để làm một cái gì đó như thế này:

public async Task<IEnumerable<Foo>> MyMethodAsync(Source src) 
{ 
    var oofs = await src.LoadOofsAsync(); 
    return oofs.Select(oof => Transform(oof)); 
} 

Tôi không phải là một chuyên gia về một trong hai async/await hoặc IEnumerable. Tuy nhiên, từ những gì tôi hiểu, sử dụng cách tiếp cận này "giết chết" những lợi ích của IEnumerable, bởi vì nhiệm vụ được chờ đợi cho đến khi toàn bộ bộ sưu tập được tải, do đó bỏ qua "sự lười biếng" của bộ sưu tập IEnumerable.

Trên các bài đăng StackOverflow khác, tôi đã đọc một số đề xuất để sử dụng Rx.NET (hoặc System.Reactive). Nhanh chóng duyệt qua tài liệu tôi đã đọc rằng IObservable<T> là sự thay thế không đồng bộ của chúng với IEnumerable<T>. Tuy nhiên, bằng cách sử dụng cách tiếp cận ngây thơ và cố gắng gõ sau đây chỉ không làm việc:

public async IObservable<Foo> MyMethodReactive(Source src) 
{ 
    var oofs = await src.LoadOofsAsync(); 
    foreach(var oof in oofs) 
    { 
     yield return Transform(oof); 
    } 
} 

tôi đã nhận một lỗi biên dịch, mà IObservable<T> không thực hiện không GetEnumerator(), cũng không GetAwaiter() - do đó nó không thể sử dụng cả hai yieldasync. Tôi đã không đọc các tài liệu hướng dẫn của Rx.NET sâu hơn, vì vậy tôi có lẽ chỉ cần sử dụng thư viện không chính xác. Nhưng tôi không muốn dành thời gian học một khuôn khổ mới để sửa đổi một phương pháp duy nhất.

Với possibilities in C# 7 mới, giờ đây bạn có thể triển khai các loại tùy chỉnh. Vì vậy, về mặt lý thuyết, tôi có thể thực hiện một số IAsyncEnumerable, định nghĩa cả hai phương thức GetEnumerator()GetAwaiter(). Tuy nhiên, từ kinh nghiệm trước đây của tôi, tôi nhớ một nỗ lực không thành công để tạo ra một thực hiện tùy chỉnh GetEnumerator() ... Tôi đã kết thúc với một danh sách đơn giản, ẩn trong một container.

Như vậy chúng ta có 4 phương pháp có thể để giải quyết các nhiệm vụ:

  1. Giữ đồng bộ mã, nhưng với IEnumerable
  2. Thay đổi nó để không đồng bộ, nhưng quấn IEnumerable trong một Task<T>
  3. Tìm hiểu và sử dụng Rx .NET (System.Reactive)
  4. Tạo một tùy chỉnh IAsyncEnumerable với các tính năng C# 7

Những lợi ích và hạn chế của mỗi lần thử này là gì? Điều nào trong số đó có tác động đáng kể nhất đối với việc sử dụng tài nguyên?

+0

Ngoài ra còn có một cách thứ 5 rất dễ dàng: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq: 'AsParallel()' –

+0

@ AdrianoRepetti AsParallel có phù hợp với nhiệm vụ I/O-bound không? – MickyD

+3

Nếu bạn quan tâm đến lợi ích "giết chóc", điều bạn thực sự nên lo lắng là 'src.LoadOofsAsync()' - rằng * đã * trả về một 'Tác vụ >'. Bao bọc thêm một số sự không đồng bộ hơn sẽ không giúp ích gì, trừ khi 'Biến đổi' thực sự tốn kém và có thể được thực hiện song song/chờ đợi một cách có ý nghĩa. –

Trả lời

2
  • Giữ đồng bộ mã, nhưng với IEnumerable
  • Thay đổi nó để không đồng bộ, nhưng quấn IEnumerable trong một nhiệm vụ
  • Tìm hiểu và sử dụng Rx.NET (System.Phản ứng)
  • Tạo 7 tính năng

những lợi ích và nhược điểm của mỗi một trong các nỗ lực như thế nào một tùy chỉnh IAsyncEnumerable với C#? Trong đó trong số đó có tác động đáng kể nhất đối với việc sử dụng tài nguyên?

Trong trường hợp của bạn, có vẻ như tùy chọn tốt nhất là Task<IEnumerable<T>>. Dưới đây là những gì mà mỗi tùy chọn vượt trội:

  1. Mã đồng bộ (hoặc mã đồng bộ song song) vượt trội khi không có I/O, nhưng sử dụng CPU nặng. Nếu bạn có mã I/O chờ đợi đồng bộ (như phương thức triển khai phương thức đầu tiên của bạn), CPU chỉ là các chu trình ghi trong khi đợi dịch vụ web phản hồi không làm gì cả.

  2. Task<IEnumerable<T>> có nghĩa là khi có hoạt động I/O để tìm nạp bộ sưu tập. Các thread chạy chờ đợi cho các hoạt động I/O sau đó có thể có một cái gì đó khác lên kế hoạch trên nó trong khi chờ đợi. Điều này nghe có vẻ giống như trường hợp của bạn.

  3. Rx là tốt nhất cho các tình huống đẩy: Nơi có dữ liệu được 'đẩy' vào mã của bạn mà bạn muốn phản hồi. Ví dụ phổ biến là các ứng dụng nhận dữ liệu giá thị trường chứng khoán hoặc ứng dụng trò chuyện.

  4. IAsyncEnumerable có nghĩa là khi bạn có một bộ sưu tập trong đó mỗi mục sẽ yêu cầu hoặc tạo tác vụ không đồng bộ. Một ví dụ: Lặp lại một tập hợp các mục và thực hiện một số loại truy vấn DB duy nhất cho mỗi mục. Nếu Transform của bạn thực tế là một phương pháp không đồng bộ I/O, thì điều này có lẽ hợp lý hơn.

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