8

Dựa trên nhiều sách và blog bao gồm this excellent one here, rõ ràng là khi viết một thư viện dll để hiển thị các phương thức async trợ giúp tức là các phương thức bao bọc, thường được coi là phương pháp hay nhất để nội bộ hoàn thành nhiệm vụ I/O của phương pháp async thực tế trên một sợi threadpool như vậy (pseudo code hiển thị dưới đây cho ngắn gọn và tôi đang sử dụng HttpClient là một ví dụ)C# async/await chaining với ConfigureAwait (false)

public Async Task<HttpResponseMessage> MyMethodAsync(..) 
{ 
    ... 
    var httpClient = new HttpClient(..); 
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false); 
    ... 
    return response; 
} 

Mấu chốt ở đây là việc sử dụng ConfigureAwait(false) để Hoàn thành nhiệm vụ IO xảy ra trên một thread threadpool thay vì trên bối cảnh thread ban đầu, do đó có khả năng ngăn ngừa deadlocks.

Câu hỏi của tôi là từ góc nhìn của người gọi. Tôi đặc biệt quan tâm đến một kịch bản có các lớp gọi phương thức giữa người gọi và cuộc gọi phương thức trên, như ví dụ sau đây.

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync 

Có đủ để có ConfigureAwait(false) vào phương pháp cuối cùng chỉ hay nên người ta cũng đảm bảo Method1AsyncMethod2Async cũng trong nội bộ gọi các phương thức async của họ với ConfigureAwait(false)? Có vẻ ngớ ngẩn khi đưa nó vào tất cả các phương pháp trung gian này, đặc biệt nếu Method1AsyncMethod2Async chỉ đơn giản là quá tải mà kết thúc gọi MyMethodAsync. Bất kỳ suy nghĩ nào, hãy khai sáng cho chúng tôi!

cập nhật với Ví dụ Vì vậy, nếu tôi có một thư viện với các phương pháp async tin sau đây,

private async Task<string> MyPrivateMethodAsync(MyClass myClass) 
{ 
    ... 
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false); 
} 

tôi nên chắc chắn các phương pháp quá tải công cộng sau cả hai cũng bao gồm ConfigureAwait (sai) như hình dưới đây?

public async Task<string> MyMethodAsync(string from) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false); 
} 
public async Task<string> MyMethodAsync(string from, string to) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false); 
} 
+0

Trong khi sử dụng 'ConfigureAwait (false)' là một thực hành tốt cho mã thư viện, việc sử dụng nó có thể có ý nghĩa riêng của nó: http://stackoverflow.com/q/28410046 – Noseratio

Trả lời

9

Chắc chắn là không. ConfigureAwait giống như tên đề xuất cấu hình await. Nó chỉ ảnh hưởng đến await cùng với nó.

ConfigureAwait thực sự trả về một kiểu khác nhau awaitable, ConfiguredTaskAwaitable thay vì Task do đó trả về một awaiter loại khác nhau ConfiguredTaskAwaiter thay vì TaskAwaiter

Nếu bạn muốn bỏ qua SynchronizationContext cho tất cả await bạn s bạn phải sử dụng ConfigureAwait(false) cho mỗi của họ.

Nếu bạn muốn giới hạn việc sử dụng ConfigureAwait(false) bạn có thể sử dụng của tôi NoSynchronizationContextScope (xem here) tại rất đầu:

async Task CallerA() 
{ 
    using (NoSynchronizationContextScope.Enter()) 
    { 
     await Method1Async(); 
    } 
} 
+1

Tôi nghĩ rằng "phải ... cho * mỗi * của chúng "hơi quá nghiêm ngặt - bạn có 2+' đang chờ đợi trong một hàng và đầu tiên thực sự trả về không đồng bộ mà phần còn lại không cần 'ConfigureAwait (false)' bởi vì bạn mất bối cảnh khi trả về cuộc gọi ... Nhưng trong thực tế 'ConfigureAwait (false)' nên có trên tất cả các cuộc gọi (hoặc không có). –

+1

@ i3arnon một câu hỏi nhanh về NoSynchronizationContextScope của bạn trông tuyệt vời và tôi muốn hiểu trường hợp sử dụng của nó. Đi với ví dụ trên của tôi, ru nói rằng, với một chút nhỏ của mã hóa thêm để bao gồm NoSynchronizationContextScope của bạn, CallerA bây giờ có thể hướng dẫn các phương pháp trên cây gọi là Method1Async -> Method2Async -> tất cả các cách để MyMethodAsync tạm thời bỏ qua SC bất kể hay không những phương thức này có ConfigureAwait (false) trong chúng? – SamDevx

+1

@SamDevx khá nhiều. Nó thực sự khắc nghiệt hơn thế. Nó tạm thời loại bỏ SC để các phương thức bên trong không có SC để bỏ qua. – i3arnon

4

Khi nhiệm vụ được chờ đợi, nó tạo ra một tương ứng TaskAwaiter để theo dõi các nhiệm vụ cũng chụp SynchronizationContext hiện tại. Sau khi nhiệm vụ hoàn thành, awaiter chạy mã sau khi chờ đợi (được gọi là sự tiếp tục) trên bối cảnh đã capture đó.

Bạn có thể ngăn chặn điều đó bằng cách gọi ConfigureAwait(false), điều này tạo ra một kiểu khác (aw) (ConfiguredTaskAwaitable) và mặt chờ tương ứng (ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) không chạy liên tục trên ngữ cảnh đã chụp.

Vấn đề là đối với mỗi await, một phiên bản khác của thiết bị chờ được tạo ra, nó không phải là thứ được chia sẻ giữa tất cả các vật đang chờ trong phương thức hoặc chương trình. Vì vậy, tốt nhất bạn nên gọi số ConfigureAwait(false) cho mỗi câu hỏi await.

Bạn có thể xem mã nguồn cho người đang chờ here.

+0

Chỉnh sửa nhỏ: khi một 'Tác vụ' được tạo, nó sẽ không * chụp 'SynchronizationContext' hiện tại. Khi 'Task.GetAwaiter()' được gọi (bởi mã được tạo bởi trình biên dịch cho 'await'), tại thời điểm đó' SynchronizationContext' bị bắt. – Noseratio

+0

@Noseratio cảm ơn nhận xét của bạn. Tôi nhận được thông tin này từ [Nguồn tác vụ] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,16e40fc537484d93). 'Task.GetAwaiter()' dường như không làm điều đó, mặc dù tôi đã nghĩ như vậy. Tôi phải thiếu một cái gì đó –

+0

Tôi không nói 'TaskAwaiter' bắt SC khi nó được tạo ra. Nó thực hiện điều đó trong việc thực hiện 'ICriticalNotifyCompletion.OnCompleted/UnsafeOnCompleted' [ở đây] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,45f10a20f8fdfd61,references), đó là khóa phương pháp của bất kỳ awaiter. – Noseratio

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