รอคอย vs Task.Wait - Deadlock?


197

ฉันไม่เข้าใจความแตกต่างระหว่างและTask.Waitawait

ฉันมีบางอย่างที่คล้ายกับฟังก์ชั่นต่อไปนี้ในบริการ ASP.NET WebAPI:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

ที่ไหนGetจะหยุดชะงัก

อะไรทำให้เกิดสิ่งนี้ ทำไมไม่ได้นี้เป็นสาเหตุปัญหาเมื่อผมใช้รอการปิดกั้นมากกว่าawait Task.Delay?


@Servy: ฉันจะกลับมาพร้อม repo ทันทีที่ฉันมีเวลา สำหรับตอนนี้มันใช้ได้กับTask.Delay(1).Wait()อันไหนดีพอ
ronag

2
Task.Delay(1).Wait()Thread.Sleep(1000)เป็นพื้นสิ่งเดียวที่แน่นอนเป็น ในรหัสการผลิตจริงมันไม่ค่อยเหมาะสม
Servy

@ronag: คุณWaitAllทำให้เกิดการหยุดชะงัก ดูลิงก์ไปยังบล็อกของฉันในคำตอบของฉันสำหรับรายละเอียดเพิ่มเติม คุณควรใช้await Task.WhenAllแทน
Stephen Cleary

6
@ronag เนื่องจากคุณมีConfigureAwait(false)การโทรเพียงครั้งเดียวไปยังBarหรือRosจะไม่หยุดชะงัก แต่เนื่องจากคุณมีการนับจำนวนที่สร้างมากกว่าหนึ่งรายการแล้วรอทุกสายแถบแรกจะหยุดชะงักวินาที หากคุณawait Task.WhenAllแทนที่จะรองานทั้งหมดเพื่อที่คุณจะไม่ได้ปิดกั้นบริบท ASP คุณจะเห็นวิธีการส่งคืนตามปกติ
Servy

2
@ronag ตัวเลือกอื่น ๆ ของคุณจะเพิ่ม.ConfigureAwait(false) ตลอดทางขึ้นต้นไม้จนกว่าคุณจะปิดกั้นว่าไม่มีอะไรวิธีที่จะเคยพยายามที่จะได้รับกลับไปบริบทหลัก ที่จะทำงาน ตัวเลือกอื่นคือการหมุนบริบทการซิงโครไนซ์ภายใน ลิงค์ หากคุณใส่Task.WhenAllในAsyncPump.Runได้อย่างมีประสิทธิภาพจะป้องกันในสิ่งที่ทั้งโดยที่คุณไม่ต้องไปConfigureAwaitที่ใดก็ได้ แต่ที่อาจเป็นทางออกที่มากเกินไปที่ซับซ้อน
Servy

คำตอบ:


270

Waitและawait- ในขณะที่แนวคิดที่คล้ายกัน - ต่างกันโดยสิ้นเชิง

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

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

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

คุณอาจพบว่าasync/ awaitบทนำของฉันเป็นประโยชน์


5
ผมคิดว่ามีความเป็น missunderstanding, Waitทำงานได้ดีawaitนะคะ
ronag

1
ชัดเจน: ใช่ถ้าฉันแทนที่ฉันawait Task.Delay(1)ด้วยTask.Delay(1).Wait()บริการทำงานได้ดีมิฉะนั้นมันจะหยุดชะงัก
ronag

5
ไม่ตัวจัดตารางงานจะไม่ทำเช่นนั้น Waitบล็อกเธรดและไม่สามารถใช้สำหรับสิ่งอื่นได้
Stephen Cleary

8
@ronag ฉันเดาว่าคุณเพิ่งผสมชื่อเมธอดของคุณและการหยุดชะงักของคุณนั้นเกิดจากรหัสการบล็อกและทำงานกับawaitรหัส ไม่เช่นนั้นหรือการหยุดชะงักไม่เกี่ยวข้องกับทั้งสองและคุณวินิจฉัยปัญหาผิดพลาด
Servy

3
@hexterminator: นี่คือการออกแบบ - มันใช้งานได้ดีสำหรับแอป UI แต่มีแนวโน้มที่จะเข้าสู่แอป ASP.NET ASP.NET Core ได้แก้ไขสิ่งนี้โดยการลบSynchronizationContextดังนั้นการบล็อกภายในการร้องขอ ASP.NET Core จะไม่หยุดชะงักอีกต่อไป
Stephen Cleary

5

ขึ้นอยู่กับสิ่งที่ฉันอ่านจากแหล่งต่าง ๆ :

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

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

บทความนี้ยังอ่านดี


3
"นั่นไม่ถูกต้องทางเทคนิคหรือไม่แล้วมีคนช่วยอธิบายได้มั้ย" - ฉันสามารถชี้แจง; คุณถามว่าเป็นคำถามหรือไม่ (ฉันแค่ต้องการให้ชัดเจนว่าคุณกำลังขอเทียบกับคำตอบ) หากคุณกำลังถาม: มันอาจทำงานได้ดีกว่าเป็นคำถามแยกต่างหาก ไม่น่าจะรวบรวมคำตอบใหม่ที่นี่เป็นคำตอบ
Marc Gravell

1
ฉันได้ตอบคำถามและถามคำถามแยกต่างหากสำหรับความสงสัยที่ฉันมีที่นี่stackoverflow.com/questions/53654006/…ขอบคุณ @MarcGravell คุณกรุณาลบการโหวตของคุณสำหรับคำตอบตอนนี้ได้ไหม?
Ayushmati

"คุณกรุณาลบการโหวตของคุณสำหรับคำตอบตอนนี้ได้ไหม?" - นั่นไม่ใช่ของฉัน ขอบคุณ♦การโหวตใด ๆ ของฉันจะมีผลทันที อย่างไรก็ตามฉันไม่คิดว่านี่เป็นคำตอบสำหรับประเด็นสำคัญของคำถามซึ่งเกี่ยวกับพฤติกรรมการหยุดชะงัก
Marc Gravell

นี่ไม่เป็นความจริง. จนกว่าจะถึงครั้งแรกที่รอไม่ถึงทุกอย่างจะถูกบล็อก
user1785960

-2

ข้อเท็จจริงสำคัญบางข้อไม่ได้ให้ไว้ในคำตอบอื่น ๆ :

"async คอย" มีความซับซ้อนมากขึ้นในระดับ CIL และทำให้ค่าใช้จ่ายหน่วยความจำและเวลา CPU

สามารถยกเลิกงานใด ๆ ได้หากเวลารอไม่สามารถยอมรับได้

ในกรณีที่ "async กำลังรอ" เราไม่มีตัวจัดการสำหรับงานดังกล่าวที่จะยกเลิกหรือตรวจสอบ

การใช้งานนั้นยืดหยุ่นกว่า "async คอย"

ฟังก์ชั่นการซิงค์ใด ๆ สามารถทำได้โดยพันด้วย async

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

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

https://github.com/dotnet/runtime/issues/36063

ฉันไม่เห็นสาเหตุที่ฉันต้องใช้ชีวิตด้วยการทำสำเนารหัสสำหรับวิธีการซิงค์และ async หรือใช้แฮ็ก

สรุป: สร้างงานด้วยตนเองและควบคุมได้ดีกว่ามาก ตัวจัดการงานให้การควบคุมมากขึ้น เราสามารถตรวจสอบงานและจัดการพวกเขา:

https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem

ขอโทษสำหรับภาษาอังกฤษของฉัน

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