ทำไมเราต้องการคำหลัก async


19

ฉันเพิ่งเริ่มเล่นกับ async / คอยใน. Net 4.5 สิ่งหนึ่งที่ฉันอยากรู้ในตอนแรกว่าทำไมคำหลัก async จึงจำเป็น คำอธิบายที่ฉันอ่านคือมันเป็นเครื่องหมายเพื่อให้คอมไพเลอร์รู้วิธีการรอบางสิ่งบางอย่าง แต่ดูเหมือนว่าคอมไพเลอร์ควรจะสามารถเข้าใจสิ่งนี้ได้โดยไม่มีคำ แล้วมันทำอะไรได้อีก?


2
นี่คือบทความบล็อกที่ดีมาก (ชุด) ซึ่งมีรายละเอียดอธิบายคำสำคัญใน C # และฟังก์ชั่นที่เกี่ยวข้องใน F #
paul

ฉันคิดว่ามันเป็นตัวทำเครื่องหมายสำหรับนักพัฒนาไม่ใช่เพื่อคอมไพเลอร์
CodesInChaos

8
บทความนี้อธิบายถึงมัน: blogs.msdn.com/b/ericlippert/archive/2010/11/11/…
svick

@Svick ขอบคุณนี่คือสิ่งที่ฉันกำลังมองหา ทำให้รู้สึกที่สมบูรณ์แบบในขณะนี้
ConditionRacer

คำตอบ:


23

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

ไม่ใช่ "เพื่อคอมไพเลอร์ให้แปลงฟังก์ชันในวิธีพิเศษ"; awaitเพียงอย่างเดียวสามารถทำได้ ทำไม? เพราะ C # แล้วมีกลไกที่การปรากฏตัวของคำหลักพิเศษในร่างกายวิธีการที่ทำให้เกิดการคอมไพเลอร์ที่จะดำเนินการมาก (และคล้ายกับที่อื่นasync/await) yieldการเปลี่ยนแปลงในร่างกายวิธีการ:

ยกเว้นว่าyieldไม่ใช่คำหลักของตนเองใน C # และเข้าใจว่าทำไมจะอธิบายasyncเช่นกัน ไม่เหมือนในภาษาส่วนใหญ่ที่สนับสนุนกลไกนี้ใน C # คุณไม่สามารถพูดได้ว่าyield value;คุณต้องพูดyield return value;แทน ทำไม? เพราะมันถูกเพิ่มเข้าไปในภาษาหลังจาก C # มีอยู่แล้วและมันค่อนข้างสมเหตุสมผลที่จะสมมติว่ามีบางคนอาจใช้yieldเป็นชื่อของตัวแปร แต่เนื่องจากไม่มีสถานการณ์จำลองที่<variable name> returnถูกต้องทางไวยากรณ์จึงyield returnได้เพิ่มภาษาเพื่อให้สามารถแนะนำเครื่องกำเนิดไฟฟ้าในขณะที่รักษาความเข้ากันได้ย้อนหลัง 100% กับรหัสที่มี

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


3
นี่เป็นสิ่งที่บทความนี้พูดถึงมากเกินไป (แต่เดิมโพสต์โดย @svick ในความคิดเห็น) แต่ไม่มีใครโพสต์ไว้เป็นคำตอบ ใช้เวลาเพียงสองปีครึ่ง! ขอบคุณ :)
ConditionRacer

นั่นไม่ได้หมายความว่าในบางรุ่นในอนาคตความต้องการในการระบุasyncคำหลักสามารถถูกทิ้งได้ เห็นได้ชัดว่ามันเป็นการพักก่อนคริสต์ศักราช แต่อาจคิดว่าส่งผลกระทบต่อโปรเจ็กต์น้อยมากและเป็นข้อกำหนดตรงไปข้างหน้าในการอัพเกรด
คำถามของ Quolonel

6

มันเปลี่ยนวิธีการจากวิธีปกติเป็นวัตถุที่มีการเรียกกลับซึ่งต้องใช้วิธีที่แตกต่างกันโดยสิ้นเชิงสำหรับการสร้างรหัส

และเมื่อสิ่งที่รุนแรงเช่นนั้นเกิดขึ้นเป็นเรื่องธรรมดาที่จะแสดงให้เห็นอย่างชัดเจน (เราเรียนรู้บทเรียนนั้นจาก C ++)


ดังนั้นนี่คือบางสิ่งสำหรับผู้อ่าน / โปรแกรมเมอร์และไม่มากสำหรับคอมไพเลอร์?
ConditionRacer

@ Justin984 มันช่วยส่งสัญญาณไปยังคอมไพเลอร์อย่างที่ต้องการเป็นพิเศษ
ratchet freak

ฉันเดาว่าฉันอยากรู้ว่าทำไมผู้แปลต้องการมัน ไม่สามารถแยกวิเคราะห์วิธีและดูว่าการรออยู่ที่ไหนสักแห่งในร่างกายวิธี
ConditionRacer

ช่วยเมื่อคุณต้องการกำหนดเวลาหลายรายการasyncในเวลาเดียวกันดังนั้นจึงไม่เรียกใช้ทีละรายการ แต่พร้อมกัน (หากมีเธรดอย่างน้อย)
ratchet freak

@ Justin984: ไม่ได้วิธีการที่กลับมาทุกTask<T>จริงมีลักษณะของนั้นasyncวิธีการ - ตัวอย่างเช่นคุณอาจจะเพียงแค่ผ่านธุรกิจตรรกะบาง / โรงงานแล้วมอบหมายให้บางอื่น ๆTask<T>วิธีการกลับมา asyncวิธีการมีประเภทของผลตอบแทนTask<T>ในลายเซ็นแต่ไม่จริงกลับพวกเขาก็กลับมาTask<T> Tเพื่อให้เข้าใจสิ่งเหล่านี้ได้โดยไม่ต้องใช้asyncคีย์เวิร์ดคอมไพเลอร์ C # จะต้องทำการตรวจสอบวิธีการทุกประเภทซึ่งอาจทำให้ช้าลงเล็กน้อยและนำไปสู่ความคลุมเครือทุกรูปแบบ
Aaronaught

4

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


ฉันอยากจะ downvote เพราะในขณะที่ย่อหน้าแรกถูกต้องการเปรียบเทียบกับการขัดจังหวะนั้นไม่ถูกต้อง
paul

ฉันเห็นว่ามันค่อนข้างคล้ายกันในแง่ของวัตถุประสงค์ แต่ฉันจะลบมันออกเพื่อความชัดเจน
วิศวกรโลก

การขัดจังหวะเป็นเหมือนเหตุการณ์มากกว่ารหัส async ในใจของฉัน - พวกเขาทำให้เกิดการสลับบริบทและทำงานให้เสร็จก่อนที่รหัสปกติจะกลับมาทำงานต่อ (และรหัสปกติอาจไม่สนใจการทำงานทั้งหมด) ในขณะที่รหัส async อาจเป็นไปได้ ยังไม่เสร็จสิ้นก่อนที่จะเรียกเธรดการเรียกคืนต่อ ฉันเห็นสิ่งที่คุณกำลังพยายามพูด wrt กลไกต่าง ๆ ที่ต้องการการใช้งานที่แตกต่างกันภายใต้ประทุน (+1 สำหรับส่วนที่เหลือ btw.)
paul

0

ตกลงนี่คือสิ่งที่ฉันทำ

มีบางสิ่งที่เรียกว่าcoroutinesซึ่งเป็นที่รู้จักกันมานานหลายทศวรรษ ( "นูและกระโดด" คลาส "มานานหลายทศวรรษ") พวกเขาเป็นภาพรวมของโปรแกรมย่อยในเช่นไม่เพียง แต่พวกเขาได้รับและการควบคุมการเปิดตัวในช่วงเริ่มต้นการทำงานและคำสั่งกลับ แต่พวกเขายังทำมันที่เฉพาะจุด ( จุดระงับ ) รูทีนย่อยเป็น coroutine ที่ไม่มีจุดพัก

พวกมันเป็นธรรมดาที่จะใช้กับมาโคร C ดังที่แสดงในบทความต่อไปนี้เกี่ยวกับ ( http://dunkels.com/adam/dunkels06protothreads.pdf ) อ่าน ฉันจะรอ...

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

สิ่งนี้จะกระทำโดยไม่แก้ไขการควบคุมการไหลของรหัสที่อธิบายไว้ใน "protothread" อย่างชัดเจน

ลองนึกภาพตอนนี้ว่าคุณมีวงวนใหญ่ที่เรียกว่า "โพรโทเท็น" ทั้งหมดนี้แล้ว

วิธีนี้มีสองข้อเสีย:

  1. คุณไม่สามารถรักษาสถานะในตัวแปรท้องถิ่นระหว่างการเริ่มต้นใหม่
  2. คุณไม่สามารถระงับ "protothread" จากความลึกการโทรโดยพลการ (คะแนนการระงับทั้งหมดต้องอยู่ที่ระดับ 0)

มีวิธีแก้ไขเฉพาะหน้าสำหรับทั้งสอง:

  1. ตัวแปรในท้องที่ทั้งหมดจะต้องถูกดึงขึ้นไปตามบริบทของโปรโตรเธด (บริบทที่ต้องการโดยความจริงที่โปรโตรเดตจะต้องจัดเก็บจุดเริ่มต้นใหม่ต่อไป)
  2. ถ้าคุณรู้สึกว่าคุณต้องเรียก protothread อื่นจาก protothread ให้ "spawn" เด็ก protothread และพักไว้จนกว่าเด็กจะเสร็จสมบูรณ์

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

และนี่คือสิ่งที่asyncและawaitทุกอย่างเกี่ยวกับ: การสร้าง coroutines (stackless)

coroutines ใน C # จะ reified เป็นวัตถุของ (ทั่วไปหรือไม่ทั่วไป) Taskระดับ

ฉันพบว่าคำหลักเหล่านี้ทำให้เข้าใจผิดมาก การอ่านจิตของฉันคือ:

  • async เป็น "สงสัย"
  • await เป็น "ระงับจนกว่าจะเสร็จสิ้น"
  • Task เป็น "อนาคต ... "

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

public Task<object> AmIACoroutine() {
    var tcs = new TaskCompletionSource<object>();
    return tcs.Task;
}

สมมติว่าasyncไม่บังคับนี่เป็น coroutine หรือฟังก์ชั่นปกติหรือไม่? คอมไพเลอร์ควรเขียนใหม่เป็น coroutine หรือไม่? ทั้งสองอาจเป็นไปได้ด้วยความหมายที่แตกต่างกันในที่สุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.