2013-02-15 19 views
37

Tài liệu MSDN dường như nêu rõ rằng asyncawait phù hợp với các tác vụ bị ràng buộc IO trong khi Task.Run nên được sử dụng cho các tác vụ ràng buộc CPU.Là không đồng bộ/await phù hợp cho các phương pháp được cả IO và CPU bị ràng buộc?

Tôi đang làm việc trên một ứng dụng thực hiện các yêu cầu HTTP để truy xuất tài liệu HTML, sau đó nó phân tích cú pháp. Tôi có một phương pháp mà trông như thế này:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync()) 
     return await Task.Run(() => LoadHtmlDocument(contentStream)); //CPU-bound 
} 

là sử dụng tốt và phù hợp này asyncawait, hay tôi quá sử dụng nó?

Trả lời

31

Có hai câu trả lời tốt đã có, nhưng để thêm tôi 0.02 ...

Nếu bạn đang nói về tốn hoạt động không đồng bộ, async/await tác phẩm xuất sắc cho cả I/O-bound và CPU- bị ràng buộc.

Tôi nghĩ rằng các tài liệu MSDN có một nghiêng nhẹ về phía sản xuất hoạt động không đồng bộ, trong trường hợp bạn muốn sử dụng TaskCompletionSource (hoặc tương tự) cho I/O-bound và Task.Run (hoặc tương tự) cho CPU-bound .Khi bạn đã tạo xong trình bao bọc Task ban đầu, tốt nhất là đã tiêu thụ theo asyncawait.

Ví dụ cụ thể của bạn, nó thực sự đi xuống bao nhiêu thời gian LoadHtmlDocument sẽ mất. Nếu bạn xóa Task.Run, bạn sẽ thực hiện nó trong cùng một ngữ cảnh gọi LoadPage (có thể trên chuỗi giao diện người dùng). Hướng dẫn của Windows 8 chỉ định rằng bất kỳ hoạt động nào lấy hơn 50ms phải được thực hiện async ... lưu ý rằng 50ms trên máy phát triển của bạn có thể dài hơn trên máy của khách hàng ...

Vì vậy, nếu bạn có thể đảm bảo rằng LoadHtmlDocument sẽ chạy dưới 50ms, bạn chỉ có thể thực hiện trực tiếp:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync()) //IO-bound 
    return LoadHtmlDocument(contentStream); //CPU-bound 
} 

Tuy nhiên, tôi muốn giới thiệu ConfigureAwait như @svick đề cập:

public async Task<HtmlDocument> LoadPage(Uri address) 
{ 
    using (var httpResponse = await new HttpClient().GetAsync(address) 
     .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound 
    using (var responseContent = httpResponse.Content) 
    using (var contentStream = await responseContent.ReadAsStreamAsync() 
     .ConfigureAwait(continueOnCapturedContext: false)) //IO-bound 
    return LoadHtmlDocument(contentStream); //CPU-bound 
} 

với ConfigureAwait, nếu t yêu cầu HTTP của anh ta không hoàn thành ngay lập tức (đồng bộ), sau đó điều này sẽ (trong trường hợp này) gây ra LoadHtmlDocument để được thực hiện trên một luồng thread thread mà không có lệnh gọi rõ ràng tới Task.Run.

Nếu bạn quan tâm đến hiệu suất async ở cấp độ này, bạn nên kiểm tra số videoMSDN article của Stephen Toub về chủ đề này. Anh ta có rất nhiều thông tin hữu ích.

12

Thích hợp với await bất kỳ thao tác nào không đồng bộ (tức là được biểu thị bằng Task).

Điểm mấu chốt là đối với các hoạt động IO, bất cứ khi nào có thể, bạn muốn sử dụng phương pháp được cung cấp, ở mức rất trung tâm, không đồng bộ, thay vì sử dụng Task.Run trên phương pháp đồng bộ chặn. Nếu bạn đang chặn một chuỗi (thậm chí là một chuỗi chủ đề luồng) trong khi thực hiện IO, bạn không tận dụng được sức mạnh thực sự của mô hình await.

Khi bạn đã tạo một Task đại diện cho hoạt động của bạn, bạn không còn quan tâm nếu đó là CPU hoặc IO bị ràng buộc. Để người gọi nó chỉ là một số hoạt động async mà cần phải được await -ed.

17

Có một số điều cần xem xét:

  • Trong một ứng dụng GUI, bạn muốn càng ít mã càng tốt để thực thi trên thread UI. Trong trường hợp đó, việc giảm tải hoạt động của CPU tới một luồng khác bằng cách sử dụng Task.Run() có lẽ là một ý tưởng hay. Mặc dù người dùng mã của bạn có thể tự làm điều đó, nếu họ muốn.
  • Trong một cái gì đó giống như ứng dụng ASP.NET, không có chuỗi giao diện người dùng và tất cả những gì bạn quan tâm là hiệu suất. Trong trường hợp đó, có một số phí sử dụng Task.Run() thay vì chạy mã trực tiếp, nhưng nó không đáng kể nếu thao tác thực sự mất một thời gian. (Ngoài ra, có một số nguyên nhân trong việc trả lại ngữ cảnh đồng bộ hóa, đó là một lý do nữa khiến bạn nên sử dụng ConfigureAwait(false) đối với hầu hết await s trong mã thư viện của mình.)
  • Nếu phương pháp của bạn không đồng bộ (BTW cũng phải được phản ánh trong tên của phương thức, không chỉ là kiểu trả về của nó), mọi người sẽ mong đợi rằng nó sẽ không chặn luồng ngữ cảnh đồng bộ hóa, ngay cả đối với công việc liên kết CPU.

Cân nhắc điều đó, tôi nghĩ sử dụng await Task.Run() là lựa chọn đúng tại đây. Nó có một số chi phí, nhưng cũng có một số lợi thế, có thể là đáng kể.

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