ทำไมคุณจะ 'รอ' วิธีการแล้วถามค่าส่งคืนทันที


24

ในบทความ MSDN นี้มีการให้รหัสตัวอย่างต่อไปนี้ (แก้ไขเล็กน้อยเพื่อความกะทัดรัด):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

FindAsyncวิธีการดึงDepartmentวัตถุโดยใช้ ID Task<Department>ของตนและผลตอบแทน จากนั้นแผนกจะถูกตรวจสอบทันทีเพื่อดูว่าเป็นโมฆะหรือไม่ ตามที่ฉันเข้าใจแล้วการขอค่าของภารกิจในลักษณะนี้จะบล็อกการเรียกใช้โค้ดจนกว่าจะส่งคืนค่าจากเมธอดที่รอคอยซึ่งส่งคืนการโทรแบบซิงโครนัสอย่างมีประสิทธิภาพ

ทำไมคุณถึงทำเช่นนี้? มันจะง่ายกว่าFind(id)ไหมถ้าจะเรียกวิธีการซิงโครนัสถ้าคุณจะบล็อกทันทีล่ะ?


อาจเกี่ยวข้องกับการนำไปปฏิบัติ ... else return null;จากนั้นคุณจะต้องตรวจสอบว่าวิธีการที่พบจริงแผนกที่คุณขอ
Jeremy Kato

ฉันไม่เห็นอะไรเลยใน asp.net แต่ในแอป destop โดยทำอย่างนี้คุณจะไม่แช่แข็ง ui
Rémi

นี่คือลิงค์ที่อธิบายแนวคิดที่รอคอยจากนักออกแบบ ... msdn.microsoft.com/en-us/magazine/hh456401.aspx
Jon Raynor

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

คำตอบ:


24

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

ไม่มาก

เมื่อคุณเรียกawait db.Departments.FindAsync(id)ใช้งานจะถูกส่งออกและเธรดปัจจุบันจะถูกส่งกลับไปยังพูลเพื่อใช้งานโดยการดำเนินการอื่น การไหลของการดำเนินการถูกปิดกั้น (ตามที่มันจะไม่คำนึงถึงการใช้งานdepartmentทันทีถ้าฉันเข้าใจสิ่งต่าง ๆ ได้อย่างถูกต้อง) แต่สิ่งอื่น ๆ ด้ายตัวเองมีอิสระที่จะใช้ในขณะที่คุณรอการดำเนินการจะเสร็จสมบูรณ์ปิดเครื่อง (และ ส่งสัญญาณโดยเหตุการณ์หรือพอร์ตเสร็จสิ้น)

ถ้าคุณเรียกd.Departments.Find(id)ใช้เธรดจะนั่งที่นั่นและรอการตอบกลับแม้ว่าการประมวลผลส่วนใหญ่จะทำบนฐานข้อมูล

คุณกำลังเพิ่มทรัพยากร CPU อย่างมีประสิทธิภาพเมื่อดิสก์ถูกผูกไว้


2
แต่ฉันคิดว่าทั้งหมดawaitได้ลงนามในส่วนที่เหลือของวิธีการเป็นความต่อเนื่องในหัวข้อเดียวกัน (มีข้อยกเว้นบางวิธี async ปั่นด้ายของตัวเอง) หรือลงนามในasyncวิธีการที่เป็นความต่อเนื่องในหัวข้อเดียวกันและอนุญาต รหัสที่เหลือเพื่อดำเนินการ (อย่างที่คุณเห็นฉันไม่ชัดเจนในการasyncทำงาน) สิ่งที่คุณกำลังอธิบายฟังดูคล้ายกับรูปแบบที่ซับซ้อนมากขึ้นThread.Sleep(untilReturnValueAvailable)
Robert Harvey

1
@RobertHarvey - มันกำหนดให้เป็นความต่อเนื่อง แต่เมื่อคุณได้ส่งงาน (และความต่อเนื่อง) ออกไปเพื่อดำเนินการไม่มีอะไรเหลือให้ทำงาน ไม่รับประกันว่าจะอยู่ในเธรดเดียวกันยกเว้นว่าคุณระบุไว้ (ผ่านConfigureAwaitiirc)
Telastyn

1
ดูคำตอบของฉัน ... ความต่อเนื่องจะกลับไปที่เธรดเดิมตามค่าเริ่มต้น
Michael Brown

2
ฉันคิดว่าฉันเห็นสิ่งที่ฉันหายไปที่นี่ เพื่อให้สิ่งนี้ให้ประโยชน์ใด ๆ กรอบงาน ASP.NET MVC จะต้องawaitเรียกpublic async Task<ActionResult> Details(int? id)ใช้ มิฉะนั้นการโทรดั้งเดิมจะปิดกั้นรอdepartment == nullการแก้ไข
Robert Harvey

2
@RobertHarvey ตามเวลาawait ..."ส่งคืน" การFindAsyncโทรเสร็จสิ้นแล้ว นั่นคือสิ่งที่รอคอย มันเรียกว่าคอยเพราะทำให้โค้ดของคุณรอสิ่งต่าง ๆ (แต่โปรดทราบว่าไม่เหมือนกับการทำให้เธรดปัจจุบันรอสิ่งต่าง ๆ )
253751

17

ฉันเกลียดที่ไม่มีตัวอย่างใดแสดงว่าเป็นไปได้ที่จะรอสองสามบรรทัดก่อนที่จะรองาน พิจารณาสิ่งนี้.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

นี่คือรหัสที่ตัวอย่างให้การสนับสนุนและคุณพูดถูก มีความรู้สึกเล็กน้อยในเรื่องนี้ มันเป็นอิสระจากเธรดหลักที่จะทำสิ่งอื่น ๆ เช่นตอบกลับไปยังอินพุต UI แต่พลังที่แท้จริงของ async / await คือฉันสามารถทำสิ่งอื่น ๆ ได้อย่างง่ายดายในขณะที่ฉันกำลังรองานที่ต้องใช้เวลานาน รหัสด้านบนจะ "บล็อก" และรอดำเนินการบรรทัดการพิมพ์จนกว่าเราจะได้ Foo & Bar ไม่จำเป็นต้องรอ เราสามารถดำเนินการได้ในขณะที่รอ

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

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


1
หืมนั่นไม่ค่อยเกี่ยวกับคอร์ / เธรดและอีกมากเกี่ยวกับการใช้การโทรแบบอะซิงโครนัสอย่างถูกต้อง
Deduplicator

คุณไม่ผิด @Dupuplicator นั่นเป็นเหตุผลที่ฉันยกคำว่า "บล็อก" ในคำตอบของฉัน เป็นการยากที่จะพูดกับสิ่งนี้โดยไม่ต้องพูดถึงหัวข้อ แต่ในขณะที่ยังคงยอมรับว่าอาจมีหรือไม่มีส่วนเกี่ยวข้องหลายเธรด
RubberDuck

ฉันขอแนะนำให้คุณระมัดระวังในการรอการเรียกวิธีอื่น .. บางครั้งคอมไพเลอร์เข้าใจผิดนี้รอและคุณได้รับข้อยกเว้นแปลกจริงๆ หลังจาก async / await ทั้งหมดเป็นเพียงซินแท็กซ์น้ำตาลคุณสามารถตรวจสอบกับ sharplab.io ว่าโค้ดที่สร้างขึ้นมีลักษณะอย่างไร ฉันสังเกตสิ่งนี้หลายต่อหลายครั้งและตอนนี้ฉันเพิ่งรอหนึ่งบรรทัดด้านบนสายที่ต้องการผล ... ไม่ต้องปวดหัวเหล่านั้น
ขับไล่

"มีเหตุผลเล็กน้อยในเรื่องนี้" - มีเหตุผลมากมายในเรื่องนี้ กรณีที่ "ทำสิ่งอื่น ๆ " สามารถใช้งานได้ในวิธีการเดียวกันนั้นไม่ใช่เรื่องปกติ เป็นเรื่องธรรมดามากที่คุณเพียงต้องการawaitและปล่อยให้เธรดทำสิ่งต่าง ๆ โดยสิ้นเชิงแทน
Sebastian Redl

เมื่อฉันพูดว่า“ มีความรู้สึกเล็ก ๆ น้อย ๆ ในเรื่องนี้” @SebastianRedl ฉันหมายถึงกรณีที่คุณสามารถเรียกใช้ทั้งสองงานในแบบขนานได้อย่างชัดเจนแทนที่จะวิ่งรอรอวิ่ง นี่เป็นเรื่องธรรมดามากกว่าที่คุณคิด ฉันเดิมพันถ้าคุณมองไปรอบ ๆ ฐานรหัสของคุณคุณจะพบโอกาส
RubberDuck

6

ดังนั้นจึงมีสิ่งอื่น ๆ อีกมากมายที่เกิดขึ้นเบื้องหลังที่นี่ Async / Await เป็นน้ำตาลประโยค อันดับแรกดูที่ลายเซ็นของฟังก์ชัน FindAsync ส่งคืนภารกิจ เมื่อคุณเห็นความมหัศจรรย์ของคำหลักแล้วจะเป็นการลบงานนั้นออกจากแผนก

ฟังก์ชั่นการโทรไม่ปิดกั้น สิ่งที่เกิดขึ้นคือการมอบหมายให้แผนกและทุกอย่างที่ทำตามคำหลักที่รอคอยได้รับการปิดกล่องและสำหรับทุกเจตนาและวัตถุประสงค์ที่ส่งผ่านไปยังวิธีการ Task.ContinueWith วิธี (ฟังก์ชั่น FindAsync จะดำเนินการโดยอัตโนมัติ

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

ดังนั้นสิ่งที่เกิดขึ้นคือคุณได้รับความมหัศจรรย์ของการปฏิบัติการ Async โดยไม่มีข้อผิดพลาด


1

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


ไม่มีการบล็อกเลย มันจะไม่ไปที่แผนก == เป็นโมฆะจนกว่าหลังจากวิธีการซิงค์เสร็จสิ้นแล้ว
Steve

1
async awaitโดยทั่วไปจะไม่สร้างเธรดใหม่และแม้ว่าคุณจะทำคุณยังต้องรอค่าแผนกนั้นเพื่อตรวจสอบว่าเป็นโมฆะหรือไม่
Robert Harvey

2
ดังนั้นโดยทั่วไปสิ่งที่คุณพูดคือเพื่อให้สิ่งนี้เป็นประโยชน์ใด ๆ เลยการเรียกร้องให้public async Task<ActionResult> มีการawaitแก้ไข
Robert Harvey

2
@ RobertHarvey ใช่คุณมีความคิด Async / Await นั้นเป็นไวรัส หากคุณ "รอ" ฟังก์ชั่นหนึ่งคุณควรรอฟังก์ชั่นที่เรียกว่า awaitไม่ควรผสมกับ.Wait()หรือ.Resultเพราะอาจทำให้เกิดการหยุดชะงักได้ เชน async / await จะสิ้นสุดที่ฟังก์ชันที่มีasync voidลายเซ็นซึ่งส่วนใหญ่จะใช้สำหรับตัวจัดการเหตุการณ์หรือสำหรับฟังก์ชันที่เรียกใช้โดยตรงจากองค์ประกอบ UI
KChaloux

1
@ Steve - " ... ดังนั้นมันจะเป็นในหัวข้อของตัวเอง. " ไม่อ่านงานยังไม่ async กระทู้และไม่ได้เป็นคู่ขนาน ซึ่งรวมถึงเอาต์พุตจริงที่แสดงว่าไม่สร้างเธรดใหม่
ToolmakerSteve

1

ฉันชอบคิดว่า "async" เหมือนสัญญาสัญญาที่ระบุว่า "ฉันสามารถดำเนินการแบบอะซิงโครนัสได้หากคุณต้องการ แต่คุณสามารถโทรหาฉันเหมือนฟังก์ชั่นซิงโครนัสอื่น ๆ ได้"

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

Task<Department> deptTask = db.Departments.FindAsync(id);

และหลังจากนั้นพูดว่า 10 บรรทัดลงฟังก์ชั่นที่คุณโทร

Department d = await deptTask;

ดังนั้นการปฏิบัติมันเป็นฟังก์ชั่นไม่ตรงกัน

มันขึ้นอยู่กับคุณ.


1
มันเหมือน "ฉันขอสงวนสิทธิ์ในการจัดการข้อความแบบอะซิงโครนัสใช้สิ่งนี้เพื่อรับผลลัพธ์"
Deduplicator

-1

"ถ้าคุณจะบล็อกทันที" anwser คือ "ใช่" เฉพาะเมื่อคุณต้องการการตอบสนองที่รวดเร็วรอ / ทำให้ตรงกัน ตัวอย่างเช่นเธรด UI มาเป็นวิธีการแบบอะซิงโครนัสเธรด UI จะกลับมาและรับฟังการคลิกปุ่มต่อไปในขณะที่โค้ดด้านล่าง "รอ" จะถูกเปิดใช้งานโดยเธรดอื่น ๆ และสุดท้ายจะได้ผลลัพธ์


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