เบลอเส้นระหว่างแอซิงก์และฟังก์ชั่นปกติใน C # 5.0


10

เมื่อเร็ว ๆ นี้ดูเหมือนว่าฉันจะไม่สามารถรับรูปแบบasync-await ที่น่าทึ่งของ C # 5.0 ได้ คุณอยู่ที่ไหนตลอดชีวิตของฉัน

ฉันตื่นเต้นอย่างมากกับไวยากรณ์ที่เรียบง่าย แต่ฉันมีปัญหาเล็กน้อย ปัญหาของฉันคือฟังก์ชั่น async มีการประกาศที่แตกต่างกันโดยสิ้นเชิงจากฟังก์ชั่นปกติ เนื่องจากมีเพียง async เท่านั้นที่สามารถรอฟังก์ชั่นอื่น ๆ ของ async ได้เมื่อฉันพยายามที่จะแปลงรหัสการบล็อกเก่า ๆ ให้เป็น async ฉันจึงมีผลกระทบโดมิโนของฟังก์ชั่นที่ฉันต้องแปลง

คนได้รับการอ้างถึงนี้เป็นทำลายผีดิบ เมื่อ async เริ่มกัดรหัสของคุณมันก็จะยิ่งใหญ่ขึ้นเรื่อย ๆ กระบวนการ porting ไม่ยากมันเป็นเพียงแค่การขว้างปาในการประกาศและการตัดค่าตอบแทนด้วยasync Task<>แต่มันเป็นเรื่องที่น่ารำคาญที่จะทำเช่นนี้ซ้ำแล้วซ้ำอีกเมื่อทำการพอร์ตรหัสซิงโครนัสเก่า

ดูเหมือนว่าฉันจะเป็นธรรมชาติมากขึ้นถ้าทั้งสองประเภทฟังก์ชั่น (async และซิงค์เก่าธรรมดา) มีไวยากรณ์เดียวกันแน่นอน หากเป็นกรณีนี้การย้ายจะใช้ความพยายามเป็นศูนย์และฉันสามารถสลับระหว่างสองรูปแบบได้อย่างไม่ลำบาก

ฉันคิดว่าสิ่งนี้สามารถใช้งานได้หากเราปฏิบัติตามกฎเหล่านี้:

  1. ฟังก์ชัน Async ไม่จำเป็นต้องมีการasyncประกาศอีกต่อไป Task<>ประเภทการกลับมาของพวกเขาจะได้ไม่ต้องถูกห่อใน คอมไพเลอร์จะระบุฟังก์ชั่น async ระหว่างการคอมไพล์ด้วยตัวเองและทำการตัดคำสั่งงาน <> โดยอัตโนมัติตามต้องการ

  2. ไม่มีการเรียกและฟังก์ชั่น async ที่ใช้ไฟและลืมได้อีก หากคุณต้องการเรียกใช้ฟังก์ชัน async คุณจะต้องรอ ฉันแทบจะไม่ใช้ไฟและลืมไปเลยและตัวอย่างทั้งหมดของเงื่อนไขการแข่งขันที่บ้าคลั่งหรือการหยุดชะงักมักจะเป็นไปตามพวกเขา ฉันคิดว่าพวกเขาสับสนเกินไปและ "ไม่ทันรู้ตัว" ด้วยความคิดแบบซิงโครนัสที่เราพยายามยกระดับ

  3. หากคุณไม่สามารถอยู่ได้โดยปราศจากไฟและลืมมันจะมีรูปแบบพิเศษสำหรับมัน ไม่ว่าในกรณีใดมันจะไม่เป็นส่วนหนึ่งของไวยากรณ์รวมที่เรียบง่ายที่ฉันกำลังพูดถึง

  4. awaitเพียงคำหลักที่คุณจำเป็นต้องหมายถึงการโทรไม่ตรงกันคือ หากคุณรอสายนั้นจะไม่ตรงกัน ถ้าคุณทำไม่ได้การโทรนั้นเป็นแบบเก่าแบบซิงโครนัส (โปรดจำไว้ว่าเราไม่มีไฟและลืมอีกต่อไป)

  5. คอมไพเลอร์จะระบุฟังก์ชัน async โดยอัตโนมัติ (เนื่องจากไม่มีการประกาศพิเศษอีกต่อไป) กฎข้อที่ 4 ทำให้ง่ายมากที่จะทำ - ถ้าฟังก์ชั่นมีการawaitโทรเข้าภายในมันจะเป็นแบบซิงค์

ใช้งานได้ไหม หรือฉันหายไปบางอย่าง ไวยากรณ์ที่เป็นเอกภาพนี้เป็นของเหลวมากขึ้นและสามารถแก้ปัญหาการเข้าทำลายซอมบี้โดยสิ้นเชิง

ตัวอย่างบางส่วน:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

หมายเหตุ: นี่เป็นความต่อเนื่องของการสนทนาที่เกี่ยวข้องที่น่าสนใจใน SO


3
คุณรอคอยการทำงานแบบอะซิงโครนัสอยู่หรือไม่? กรุณาบอกฉันว่าคุณไม่ทำเช่นนี้ทันทีหลังจากที่ยิงพวกเขา ...
จิมมี่ฮอฟฟา

1
นอกจากนี้หนึ่งในสิ่งที่ยอดเยี่ยมเกี่ยวกับ async ก็คือคุณไม่จำเป็นต้องทำawaitทันที var task = FooAsync(); Bar(); await task;คุณสามารถทำสิ่งที่ชอบ ฉันจะทำสิ่งนี้ในข้อเสนอของคุณได้อย่างไร
svick

3
ดังนั้นจะมีการอภิปรายหรือไม่? BFG-3000 ของฉันอยู่ที่ไหน ...
Robert Harvey

2
@talkol คุณคิดว่าการเขียนโปรแกรมแบบขนานเป็นสิ่งลามกหรือเปล่า? asyncนั่นเป็นมุมมองที่น่าสนใจที่จะพูดน้อยเมื่อคุณกำลังพูดคุยเกี่ยวกับ ฉันคิดว่าเป็นหนึ่งในข้อดีที่ยิ่งใหญ่ของasync- await: ช่วยให้คุณสามารถเขียนการดำเนินการแบบอะซิงโครนัสได้อย่างง่ายดาย (ไม่ใช่แค่เพียงเริ่มต้นง่ายรอ A เริ่ม B รอวิธี B) awaitและมีอยู่แล้วไวยากรณ์พิเศษว่าสำหรับวัตถุประสงค์นี้มันเรียกว่า
svick

1
@svick haha ​​ตอนนี้เราไปที่นั่นแล้ว :) ฉันไม่คิดว่า prog แบบขนานนั้นหยาบคาย แต่ฉันคิดว่าการทำมันด้วย async-await คือ Async-await เป็นน้ำตาลเชิงประโยคสำหรับรักษาสถานะของจิตใจของคุณแบบซิงโครนัสโดยไม่ต้องจ่ายราคาปิดกั้น หากคุณกำลังคิดแบบขนานฉันจะขอให้คุณใช้รูปแบบที่แตกต่าง
ทอล์ค

คำตอบ:


9

คำถามของคุณได้รับคำตอบแล้วในคำถาม SO ที่คุณเชื่อมโยง

จุดประสงค์ของการ async / await คือการทำให้การเขียนโค้ดง่ายขึ้นในโลกที่มีการปฏิบัติการแฝงสูง การดำเนินการส่วนใหญ่ของคุณไม่ใช่ความล่าช้าสูง

เมื่อWinRTออกมาเป็นครั้งแรกนักออกแบบได้อธิบายถึงวิธีที่พวกเขาตัดสินใจว่าการดำเนินการใดจะเป็นแบบซิงค์ พวกเขาตัดสินใจว่าอะไรก็ตามที่ใช้เวลา 50ms ขึ้นไปนั้นจะเป็นแบบอะซิงก์และส่วนที่เหลือของวิธีการจะเป็นวิธีการธรรมดาแบบไม่ซิงโครนัส

ต้องมีวิธีการหลายวิธีในการเขียนใหม่เพื่อให้ตรงกันหรือไม่ ประมาณ 10 เปอร์เซ็นต์ของพวกเขา อีก 90% ไม่ได้รับผลกระทบเลย

Eric Lippert อธิบายต่อไปในรายละเอียดทางเทคนิคที่ค่อนข้างสมเหตุสมผลว่าทำไมพวกเขาถึงเลือกที่จะไม่ใช้วิธีการเดียวที่เหมาะกับทุกคน โดยทั่วไปเขาบอกว่าasyncและawaitเป็นการใช้งานบางส่วนของรูปแบบการส่งต่ออย่างต่อเนื่องและการปรับรูปแบบดังกล่าวให้เหมาะสมกับทุกกรณีเป็นปัญหาที่ยาก


โปรดทราบความแตกต่างที่สำคัญระหว่างคำถาม SO และคำถามนี้ ดังนั้นถามว่าทำไมไม่ทำทุกอย่างให้ตรงกัน ที่นี่เราไม่แนะนำให้ทำเช่นนี้เราขอแนะนำให้สร้าง 10% async เพียงแค่ใช้ไวยากรณ์แบบเดียวกันสำหรับทุกอย่าง การใช้ซินแทกซ์แบบใกล้ชิดมีข้อได้เปรียบที่คุณสามารถเปลี่ยนได้ง่ายขึ้นซึ่ง 10% เป็นแบบอะซิงโครนัสโดยไม่ต้องทนทุกข์ทรมานกับผลกระทบจากการเปลี่ยนแปลงเหล่านี้
talkol

ฉันไม่ค่อยชัดเจนว่าทำไมasyncจะทำให้เกิดการระบาดของซอมบี้ แม้ว่าวิธีหนึ่งจะเรียกใช้อีก 10 วิธี แต่คุณไม่จำเป็นต้องasyncอยู่ในระดับบนสุดใช่หรือไม่
Robert Harvey

6
สมมติว่ารหัสปัจจุบันของฉัน 100% มีการซิงค์ ตอนนี้ฉันมีฟังก์ชั่นระดับลีฟภายในที่สอบถาม DB ที่ฉันต้องการเปลี่ยนเป็น async ตอนนี้เพื่อให้เป็น async ฉันต้องการให้ผู้โทรเป็น async และผู้โทรต้องเป็น async และต่อ ๆ ไปจนถึงระดับบนสุด แน่นอนฉันกำลังพูดถึงกรณีที่ห่วงโซ่ทั้งหมดจะรอคอย (เพื่อให้การออกแบบซิงโครนัสของรหัสหรือ
val Return Return
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.