Một sự khác biệt chính là trong việc truyền ngoại lệ. Một ngoại lệ, ném ra bên trong một async Task
phương pháp, được lưu trữ trong các trở Task
đối tượng và vẫn im lìm cho đến khi công việc được quan sát qua await task
, task.Wait()
, task.Result
hoặc task.GetAwaiter().GetResult()
. Nó được truyền theo cách này ngay cả khi được ném ra từ phần đồng bộ của async
phương thức.
Hãy xem xét đoạn mã sau, ở đâu OneTestAsync
và AnotherTestAsync
hoạt động hoàn toàn khác:
static async Task OneTestAsync(int n)
{
await Task.Delay(n);
}
static Task AnotherTestAsync(int n)
{
return Task.Delay(n);
}
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
Task task = null;
try
{
task = whatTest(n);
Console.Write("Press enter to continue");
Console.ReadLine();
task.Wait();
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
}
Nếu tôi gọi DoTestAsync(OneTestAsync, -2)
, nó tạo ra kết quả sau:
Nhấn Enter để tiếp tục
Lỗi: Đã xảy ra một hoặc nhiều lỗi. Xóa Task.Delay
Lỗi: thứ 2
Lưu ý, tôi phải nhấn Enterđể xem nó.
Bây giờ, nếu tôi gọi DoTestAsync(AnotherTestAsync, -2)
, quy trình làm việc của mã bên trong DoTestAsync
khá khác, và đầu ra cũng vậy. Lần này, tôi không được yêu cầu nhấn Enter:
Lỗi: Giá trị phải là -1 (biểu thị thời gian chờ vô hạn), 0 hoặc số nguyên dương.
Tên thông số: mili giâyDelayError: 1st
Trong cả hai trường hợp, Task.Delay(-2)
ném vào đầu, trong khi xác thực các tham số của nó. Đây có thể là một kịch bản dựng sẵn, nhưng trên lý thuyếtTask.Delay(1000)
cũng có thể xảy ra, ví dụ: khi API hẹn giờ hệ thống cơ bản bị lỗi.
Một lưu ý nhỏ, logic truyền lỗi vẫn khác nhau đối với async void
các phương thức (trái ngược với async Task
các phương thức). Một ngoại lệ được đưa ra bên trong một async void
phương thức sẽ ngay lập tức được ném lại trên ngữ cảnh đồng bộ hóa của luồng hiện tại (qua SynchronizationContext.Post
), nếu luồng hiện tại có một ( SynchronizationContext.Current != null)
. Nếu không, nó sẽ được ném lại qua ThreadPool.QueueUserWorkItem
). Người gọi không có cơ hội xử lý ngoại lệ này trên cùng một khung ngăn xếp.
Tôi đã đăng thêm một số chi tiết về hành vi xử lý ngoại lệ TPL ở đây và ở đây .
Hỏi : Có thể bắt chước hành vi lan truyền ngoại lệ của các async
phương thức đối với các phương thức không Task
dựa trên cơ sở không đồng bộ, để sau này không ném vào cùng một khung ngăn xếp không?
A : Nếu thực sự cần thiết, thì có, có một mẹo cho điều đó:
async Task<int> MethodAsync(int arg)
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
}
Task<int> MethodAsync(int arg)
{
var task = new Task<int>(() =>
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
});
task.RunSynchronously(TaskScheduler.Default);
return task;
}
Tuy nhiên, lưu ý rằng trong một số điều kiện nhất định (như khi nó quá sâu trong ngăn xếp), RunSynchronously
vẫn có thể thực thi không đồng bộ.
Một điểm khác biệt đáng chú ý là
các async
/ await
phiên bản là dễ bị chết khóa trên một bối cảnh đồng bộ hóa không phải mặc định . Ví dụ: phần sau sẽ bị khóa trong ứng dụng WinForms hoặc WPF:
static async Task TestAsync()
{
await Task.Delay(1000);
}
void Form_Load(object sender, EventArgs e)
{
TestAsync().Wait();
}
Thay đổi nó thành một phiên bản không đồng bộ và nó sẽ không bị khóa:
Task TestAsync()
{
return Task.Delay(1000);
}
Bản chất của dead-lock đã được Stephen Cleary giải thích rõ trong blog của mình .
await
/async
ở tất cả :)