Mã của bạn không làm những gì bạn có thể nghĩ nó làm. Các phương thức async trở lại ngay lập tức sau khi phương thức bắt đầu chờ kết quả async. Thật sâu sắc khi sử dụng theo dõi để điều tra cách mã thực sự hoạt động.
Mã dưới đây thực hiện như sau:
- Tạo 4 nhiệm vụ
- Mỗi tác vụ sẽ tăng không đồng bộ một số và trả về số tăng
- Khi kết quả không đồng bộ đã đến, nó được theo dõi.
static TypeHashes _type = new TypeHashes(typeof(Program));
private void Run()
{
TracerConfig.Reset("debugoutput");
using (Tracer t = new Tracer(_type, "Run"))
{
for (int i = 0; i < 4; i++)
{
DoSomeThingAsync(i);
}
}
Application.Run(); // Start window message pump to prevent termination
}
private async void DoSomeThingAsync(int i)
{
using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
{
t.Info("Hi in DoSomething {0}",i);
try
{
int result = await Calculate(i);
t.Info("Got async result: {0}", result);
}
catch (ArgumentException ex)
{
t.Error("Got argument exception: {0}", ex);
}
}
}
Task<int> Calculate(int i)
{
var t = new Task<int>(() =>
{
using (Tracer t2 = new Tracer(_type, "Calculate"))
{
if( i % 2 == 0 )
throw new ArgumentException(String.Format("Even argument {0}", i));
return i++;
}
});
t.Start();
return t;
}
Khi bạn quan sát dấu vết
22:25:12.649 02172/02820 { AsyncTest.Program.Run
22:25:12.656 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.657 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0
22:25:12.658 02172/05220 { AsyncTest.Program.Calculate
22:25:12.659 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.659 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1
22:25:12.660 02172/02756 { AsyncTest.Program.Calculate
22:25:12.662 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.662 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2
22:25:12.662 02172/02820 { AsyncTest.Program.DoSomeThingAsync
22:25:12.662 02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3
22:25:12.664 02172/02756 } AsyncTest.Program.Calculate Duration 4ms
22:25:12.666 02172/02820 } AsyncTest.Program.Run Duration 17ms ---- Run has completed. The async methods are now scheduled on different threads.
22:25:12.667 02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1
22:25:12.667 02172/02756 } AsyncTest.Program.DoSomeThingAsync Duration 8ms
22:25:12.667 02172/02756 { AsyncTest.Program.Calculate
22:25:12.665 02172/05220 Exception AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.668 02172/02756 Exception AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
22:25:12.724 02172/05220 } AsyncTest.Program.Calculate Duration 66ms
22:25:12.724 02172/02756 } AsyncTest.Program.Calculate Duration 57ms
22:25:12.725 02172/05220 Error AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0
Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106
22:25:12.725 02172/02756 Error AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2
Server stack trace:
at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124
at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Exception rethrown at [0]:
at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()
at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()
at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0
22:25:12.726 02172/05220 } AsyncTest.Program.DoSomeThingAsync Duration 70ms
22:25:12.726 02172/02756 } AsyncTest.Program.DoSomeThingAsync Duration 64ms
22:25:12.726 02172/05220 { AsyncTest.Program.Calculate
22:25:12.726 02172/05220 } AsyncTest.Program.Calculate Duration 0ms
22:25:12.726 02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3
22:25:12.726 02172/05220 } AsyncTest.Program.DoSomeThingAsync Duration 64ms
Bạn sẽ nhận thấy rằng phương thức Run hoàn thành trên luồng 2820 trong khi chỉ có một luồng con hoàn thành (2756). Nếu bạn đặt thử / bắt xung quanh phương thức chờ đợi của mình, bạn có thể "bắt" ngoại lệ theo cách thông thường mặc dù mã của bạn được thực thi trên một luồng khác khi tác vụ tính toán kết thúc và việc thực hiện của bạn được thực hiện.
Phương pháp tính toán tự động theo dõi ngoại lệ bị ném vì tôi đã sử dụng ApiChange.Api.dll từ công cụ ApiChange . Truy tìm và phản xạ giúp rất nhiều để hiểu những gì đang xảy ra. Để thoát khỏi luồng, bạn có thể tạo các phiên bản GetAwaiter BeginAwait và EndAwait của riêng bạn và không phải là một nhiệm vụ mà là Lười và theo dõi bên trong các phương thức mở rộng của riêng bạn. Sau đó, bạn sẽ hiểu rõ hơn về trình biên dịch và TPL làm gì.
Bây giờ bạn thấy rằng không có cách nào để thử lại / bắt lại ngoại lệ của bạn vì không còn khung ngăn xếp nào cho bất kỳ ngoại lệ nào được truyền từ đó. Mã của bạn có thể đang làm một cái gì đó hoàn toàn khác sau khi bạn đã bắt đầu các hoạt động không đồng bộ. Nó có thể gọi Thread.S ngủ hoặc thậm chí chấm dứt. Miễn là còn một luồng tiền cảnh, ứng dụng của bạn sẽ vui vẻ tiếp tục thực thi các tác vụ không đồng bộ.
Bạn có thể xử lý ngoại lệ bên trong phương thức async sau khi thao tác không đồng bộ của bạn kết thúc và gọi lại vào luồng UI. Cách được đề xuất để thực hiện việc này là với TaskScheduler.FromSyn syncizationContext . Điều đó chỉ hoạt động nếu bạn có một giao diện người dùng và nó không quá bận rộn với những thứ khác.