รหัสของคุณไม่ได้ทำในสิ่งที่คุณคิดว่าทำ เมธอด Async กลับมาทันทีหลังจากเมธอดเริ่มรอผลการซิงค์ การใช้การติดตามอย่างละเอียดเพื่อตรวจสอบว่ารหัสทำงานอย่างไร
รหัสด้านล่างทำสิ่งต่อไปนี้:
- สร้าง 4 งาน
- แต่ละงานจะเพิ่มตัวเลขแบบไม่พร้อมกันและคืนจำนวนที่เพิ่มขึ้น
- เมื่อผลลัพธ์ของการซิงค์มาถึงแล้วจะมีการติดตาม
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;
}
เมื่อคุณสังเกตเห็นร่องรอย
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
คุณจะสังเกตเห็นว่าวิธีการเรียกใช้เสร็จสมบูรณ์ในเธรด 2820 ในขณะที่เพียงหนึ่งเธรดลูกได้เสร็จสิ้นแล้ว (2756) หากคุณลอง / ตรวจสอบวิธีการรอของคุณคุณสามารถ "ตรวจจับ" ข้อยกเว้นในแบบปกติแม้ว่ารหัสของคุณจะถูกเรียกใช้งานในเธรดอื่นเมื่องานการคำนวณเสร็จสิ้น
วิธีการคำนวณติดตามข้อยกเว้นที่ส่งออกมาโดยอัตโนมัติเพราะฉันใช้ ApiChange.Api.dll จากเครื่องมือApiChange การสืบค้นกลับและตัวสะท้อนแสงช่วยให้หลาย ๆ คนเข้าใจสิ่งที่เกิดขึ้น ในการกำจัดเธรดคุณสามารถสร้าง GetAwaiter รุ่น BeginAwait และ EndAwait รุ่นของคุณเองและไม่ห่อหุ้มงาน แต่อย่างเช่น Lazy และ trace ภายในเมธอดส่วนขยายของคุณเอง จากนั้นคุณจะได้รับความเข้าใจที่ดีขึ้นว่าคอมไพเลอร์และสิ่งที่ TPL ทำ
ตอนนี้คุณเห็นว่าไม่มีทางที่จะลอง / จับข้อยกเว้นของคุณกลับมาได้เนื่องจากไม่มีกรอบสแต็กเหลือสำหรับข้อยกเว้นใด ๆ ที่จะเผยแพร่ รหัสของคุณอาจทำสิ่งที่แตกต่างอย่างสิ้นเชิงหลังจากที่คุณเริ่มต้นการดำเนินการ async มันอาจจะเรียก Thread.Sleep หรือแม้กระทั่งยุติ ตราบใดที่มีเธรดด้านหน้าเบื้องหน้าเหลือแอปพลิเคชันของคุณจะยังคงทำงานแบบอะซิงโครนัสอย่างต่อเนื่อง
คุณสามารถจัดการข้อยกเว้นภายในเมธอด async หลังจากการดำเนินการแบบอะซิงโครนัสของคุณเสร็จสิ้นและโทรกลับไปยังเธรด UI วิธีที่แนะนำให้ทำเช่นนี้คือมีTaskScheduler.FromSynchronizationContext ใช้งานได้เฉพาะเมื่อคุณมีเธรด UI และไม่ยุ่งกับสิ่งอื่น ๆ