Vấn đề là bạn đang sử dụng lớp không chung chung Task
, điều đó không có nghĩa là tạo ra kết quả. Vì vậy, khi bạn tạo Task
cá thể thông qua một ủy nhiệm async:
Task myTask = new Task(async () =>
... Đại biểu được coi là async void
. Một async void
không phải là một Task
, nó không thể được chờ đợi, ngoại lệ của nó không thể được xử lý, và đó là một nguồn gốc của hàng ngàn câu hỏi được thực hiện bởi các lập trình viên thất vọng ở đây trong StackOverflow và các nơi khác. Giải pháp là sử dụng Task<TResult>
lớp chung , vì bạn muốn trả về một kết quả và kết quả là một kết quả khác Task
. Vì vậy, bạn phải tạo một Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Bây giờ khi bạn Start
ở bên ngoài, Task<Task>
nó sẽ được hoàn thành gần như ngay lập tức vì công việc của nó chỉ là tạo ra bên trong Task
. Sau đó, bạn sẽ phải chờ đợi bên trong Task
là tốt. Đây là cách nó có thể được thực hiện:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Bạn có hai lựa chọn thay thế. Nếu bạn không cần một tài liệu tham khảo rõ ràng cho bên trong Task
thì bạn chỉ có thể chờ đợi bên ngoài Task<Task>
hai lần:
await await myTask;
... hoặc bạn có thể sử dụng phương thức tiện ích mở rộng tích Unwrap
hợp kết hợp các tác vụ bên ngoài và bên trong thành một:
await myTask.Unwrap();
Việc hủy ghép này tự động xảy ra khi bạn sử dụng Task.Run
phương thức phổ biến hơn nhiều để tạo ra các tác vụ nóng, vì vậy Unwrap
ngày nay không được sử dụng thường xuyên.
Trong trường hợp bạn quyết định rằng đại biểu không đồng bộ của bạn phải trả về một kết quả, ví dụ a string
, thì bạn nên khai báo myTask
biến là loại Task<Task<string>>
.
Lưu ý: Tôi không tán thành việc sử dụng các hàm Task
tạo để tạo các tác vụ lạnh. Vì thông thường người ta thường cau mày, vì những lý do tôi không thực sự biết, nhưng có lẽ vì nó hiếm khi được sử dụng nên nó có khả năng khiến người dùng / người bảo trì / người đánh giá mã không biết đến bất ngờ.
Lời khuyên chung: Hãy cẩn thận mỗi khi bạn cung cấp một đại biểu không đồng bộ làm đối số cho một phương thức. Phương pháp này lý tưởng nhất là mong đợi một Func<Task>
đối số (có nghĩa là hiểu các đại biểu không đồng bộ) hoặc ít nhất là một Func<T>
đối số (có nghĩa là ít nhất là đối số được tạo Task
sẽ không bị bỏ qua). Trong trường hợp không may là phương pháp này chấp nhận Action
, đại biểu của bạn sẽ được coi là async void
. Điều này hiếm khi là những gì bạn muốn, nếu có bao giờ.