ข้อแตกต่างที่สำคัญประการหนึ่งคือการขยายพันธุ์ข้อยกเว้น ข้อยกเว้นโยนภายในasync Task
วิธีการได้รับการจัดเก็บไว้ในที่ส่งคืนTask
วัตถุและยังคงอยู่เฉยๆจนกว่างานที่ได้รับการตั้งข้อสังเกตผ่านawait task
, task.Wait()
, หรือtask.Result
task.GetAwaiter().GetResult()
มันแพร่กระจายด้วยวิธีนี้แม้ว่าจะถูกโยนจากส่วนซิงโครนัสของasync
วิธีการก็ตาม
พิจารณารหัสต่อไปนี้โดยที่OneTestAsync
และAnotherTestAsync
ทำงานค่อนข้างแตกต่างกัน:
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);
}
}
ถ้าฉันเรียกDoTestAsync(OneTestAsync, -2)
มันจะสร้างผลลัพธ์ต่อไปนี้:
กด Enter เพื่อดำเนินการต่อ
ข้อผิดพลาด: เกิดข้อผิดพลาดอย่างน้อยหนึ่งข้อรอ Task.Delay
ข้อผิดพลาด: 2nd
หมายเหตุผมต้องกดEnterดู
ตอนนี้ถ้าฉันโทรDoTestAsync(AnotherTestAsync, -2)
ไปเวิร์กโฟลว์ของโค้ดข้างในDoTestAsync
จะแตกต่างกันมากดังนั้นผลลัพธ์ก็เช่นกัน ครั้งนี้ฉันไม่ได้ขอให้กดEnter:
ข้อผิดพลาด: ค่าต้องเป็น -1 (หมายถึงการหมดเวลาไม่สิ้นสุด), 0 หรือจำนวนเต็มบวก
ชื่อพารามิเตอร์: millisecondsDelayError: 1st
ในทั้งสองกรณีจะTask.Delay(-2)
พ่นที่จุดเริ่มต้นในขณะที่ตรวจสอบความถูกต้องของพารามิเตอร์ นี่อาจเป็นสถานการณ์ที่สร้างขึ้น แต่ในทางทฤษฎีTask.Delay(1000)
อาจทำให้เกิดปัญหาได้เช่นกันเช่นเมื่อ API ตัวจับเวลาระบบที่อยู่เบื้องหลังล้มเหลว
หมายเหตุด้านข้างตรรกะการเผยแพร่ข้อผิดพลาดยังแตกต่างกันสำหรับasync void
วิธีการ (ซึ่งต่างจากasync Task
วิธีการ) ข้อยกเว้นที่เกิดขึ้นภายในasync void
เมธอดจะถูกโยนซ้ำทันทีบนบริบทการซิงโครไนซ์ของเธรดปัจจุบัน (ผ่านSynchronizationContext.Post
) หากเธรดปัจจุบันมีหนึ่งเธรด ( SynchronizationContext.Current != null)
มิฉะนั้นจะถูกโยนซ้ำผ่านThreadPool.QueueUserWorkItem
) ผู้เรียกไม่มีโอกาสจัดการกับข้อยกเว้นนี้บนสแต็กเฟรมเดียวกัน
ผมโพสต์รายละเอียดบางอย่างเพิ่มเติมเกี่ยวกับพฤติกรรมการจัดการข้อยกเว้น TPL ที่นี่และที่นี่
ถาม : เป็นไปได้ไหมที่จะเลียนแบบพฤติกรรมการแพร่กระจายข้อยกเว้นของasync
เมธอดสำหรับเมธอดที่ไม่ใช่ async Task
เพื่อไม่ให้ส่วนหลังอยู่บนสแต็กเฟรมเดียวกัน
ตอบ : หากจำเป็นจริงๆใช่มีเคล็ดลับสำหรับสิ่งนั้น:
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;
}
อย่างไรก็ตามโปรดทราบว่าภายใต้เงื่อนไขบางประการ (เช่นเมื่อมันอยู่ลึกเกินไปบนสแต็ก) RunSynchronously
ยังสามารถดำเนินการแบบอะซิงโครนัสได้
อีกความแตกต่างที่โดดเด่นก็คือว่า
/ รุ่นอื่น ๆ มีแนวโน้มที่จะตายล็อคในบริบทการประสานไม่ใช่ค่าเริ่มต้น เช่นสิ่งต่อไปนี้จะล็อคตายในแอปพลิเคชัน WinForms หรือ WPF:
async
await
static async Task TestAsync()
{
await Task.Delay(1000);
}
void Form_Load(object sender, EventArgs e)
{
TestAsync().Wait();
}
เปลี่ยนเป็นเวอร์ชันที่ไม่ใช่ async และจะไม่ล็อคตาย:
Task TestAsync()
{
return Task.Delay(1000);
}
ธรรมชาติของคนตายล็อคจะมีการอธิบายอย่างดีจากสตีเฟ่นเคลียร์ของเขาในบล็อก
await
/async
เลย :)