Lập trình không đồng bộ "phát triển" thông qua cơ sở mã. Nó đã được so sánh với một virus zombie . Giải pháp tốt nhất là cho phép nó phát triển, nhưng đôi khi điều đó là không thể.
Tôi đã viết một vài loại trong thư viện Nito.AsyncEx của mình để xử lý cơ sở mã không đồng bộ một phần. Không có giải pháp nào hoạt động trong mọi tình huống.
Giải pháp A
Nếu bạn có một phương pháp không đồng bộ đơn giản mà không cần đồng bộ hóa trở lại bối cảnh của nó, thì bạn có thể sử dụng Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Bạn không muốn sử dụng Task.Wait
hoặc Task.Result
vì chúng bao gồm các ngoại lệ AggregateException
.
Giải pháp này chỉ phù hợp nếu MyAsyncMethod
không đồng bộ hóa trở lại bối cảnh của nó. Nói cách khác, mỗi await
trong MyAsyncMethod
nên kết thúc bằng ConfigureAwait(false)
. Điều này có nghĩa là nó không thể cập nhật bất kỳ thành phần UI nào hoặc truy cập vào bối cảnh yêu cầu ASP.NET.
Giải pháp B
Nếu MyAsyncMethod
không cần phải đồng bộ hóa trở lại bối cảnh của nó, thì bạn có thể sử dụng AsyncContext.RunTask
để cung cấp một bối cảnh lồng nhau:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Cập nhật ngày 14 tháng 4 năm 2014: Trong các phiên bản gần đây hơn của thư viện, API như sau:
var result = AsyncContext.Run(MyAsyncMethod);
(Bạn có thể sử dụng Task.Result
trong ví dụ này vì RunTask
sẽ truyền Task
ngoại lệ).
Lý do bạn có thể cần AsyncContext.RunTask
thay vì Task.WaitAndUnwrapException
là vì khả năng bế tắc khá tinh tế xảy ra trên WinForms / WPF / SL / ASP.NET:
- Phương thức đồng bộ gọi phương thức không đồng bộ, thu được a
Task
.
- Phương thức đồng bộ thực hiện chờ chặn trên
Task
.
- Các
async
phương pháp sử dụng await
mà không cần ConfigureAwait
.
- Không
Task
thể hoàn thành trong tình huống này vì nó chỉ hoàn thành khi async
phương thức kết thúc; các async
phương pháp có thể không đầy đủ vì nó đang cố gắng sắp xếp tiếp nối của nó đến SynchronizationContext
, và WinForms / WPF / SL / ASP.NET sẽ không cho phép việc tiếp tục chạy vì phương pháp đồng bộ vẫn đang chạy trong bối cảnh đó.
Đây là một lý do tại sao nên sử dụng ConfigureAwait(false)
trong mọi async
phương pháp càng nhiều càng tốt.
Giải pháp C
AsyncContext.RunTask
sẽ không hoạt động trong mọi kịch bản. Ví dụ: nếu async
phương thức chờ một cái gì đó yêu cầu sự kiện UI hoàn thành, thì bạn sẽ bế tắc ngay cả với bối cảnh lồng nhau. Trong trường hợp đó, bạn có thể bắt đầu async
phương thức trên nhóm luồng:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Tuy nhiên, giải pháp này đòi hỏi một MyAsyncMethod
thứ sẽ hoạt động trong bối cảnh nhóm luồng. Vì vậy, nó không thể cập nhật các thành phần UI hoặc truy cập vào bối cảnh yêu cầu ASP.NET. Và trong trường hợp đó, bạn cũng có thể thêm ConfigureAwait(false)
vào các await
câu lệnh của nó và sử dụng giải pháp A.
Cập nhật, 2019-05-01: "Các thực tiễn tồi tệ nhất" hiện tại có trong một bài viết MSDN tại đây .