เหตุใดจึงมีคลาส async state ของเครื่อง (และไม่ใช่โครงสร้าง) ใน Roslyn


87

ลองพิจารณาวิธีการ async ที่ง่ายมากนี้:

static async Task myMethodAsync() 
{
    await Task.Delay(500);
}

เมื่อฉันรวบรวมสิ่งนี้ด้วย VS2013 (คอมไพเลอร์ก่อน Roslyn) สถานะเครื่องที่สร้างขึ้นจะเป็นโครงสร้าง

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{  
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

เมื่อฉันรวบรวมด้วย VS2015 (Roslyn) รหัสที่สร้างขึ้นคือ:

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

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

ไม่มีใครรู้ว่าทำไมถึงเปลี่ยนอีกครั้งใน Roslyn? (ฉันไม่มีปัญหาเกี่ยวกับเรื่องนี้ฉันรู้ว่าการเปลี่ยนแปลงนี้โปร่งใสและไม่ได้เปลี่ยนพฤติกรรมของโค้ดใด ๆ ฉันแค่อยากรู้)

แก้ไข:

คำตอบจาก @Damien_The_Unbeliever (และซอร์สโค้ด :)) imho อธิบายทุกอย่าง ลักษณะการทำงานที่อธิบายไว้ของ Roslyn ใช้สำหรับการสร้างดีบักเท่านั้น (และจำเป็นต้องใช้เนื่องจากข้อ จำกัด CLR ที่กล่าวถึงในความคิดเห็น) ใน Release ยังสร้างโครงสร้าง (พร้อมประโยชน์ทั้งหมดของสิ่งนั้น .. ) ดังนั้นนี่จึงเป็นโซลูชันที่ชาญฉลาดมากในการรองรับทั้ง Edit และ Continue และประสิทธิภาพที่ดีขึ้นในการผลิต สิ่งที่น่าสนใจขอบคุณสำหรับทุกคนที่เข้าร่วม!


2
ฉันสงสัยว่าพวกเขาตัดสินใจว่าความซับซ้อน (โครงสร้างที่เปลี่ยนแปลงได้) ไม่คุ้มค่า asyncเมธอดมักจะมีจุดอะซิงโครนัสที่แท้จริง - awaitซึ่งให้ผลการควบคุมซึ่งจะต้องมีการวางโครงสร้างไว้ ฉันเชื่อว่าโครงสร้างจะช่วยลดความกดดันของหน่วยความจำสำหรับasyncวิธีการที่เกิดขึ้นเพื่อทำงานพร้อมกันเท่านั้น
Stephen Cleary

คำตอบ:


112

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

และที่นี่ในบรรทัดที่ 60 ของ AsyncRewriterเราพบ:

// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;

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


18
จับดีมาก! และจากสิ่งนี้นี่คือสิ่งที่ฉันค้นพบ: สิ่งนี้จะเกิดขึ้นก็ต่อเมื่อคุณสร้างมันในการดีบัก (สมเหตุสมผลนั่นคือเมื่อคุณทำ EnC .. ) แต่ใน Release พวกเขาสร้างโครงสร้าง (เห็นได้ชัดว่า EnableEditAndContinue เป็นเท็จในกรณีนั้น .. .). Btw. ฉันพยายามค้นหารหัสด้วย แต่ไม่พบสิ่งนี้ ขอบคุณมาก!
gregkalapos

3

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

ประสิทธิภาพ "โบนัส" ของโครงสร้างมักจะเป็นการแลกเปลี่ยน โดยทั่วไปคุณจะได้รับสิ่งต่อไปนี้:

  • ความหมายเชิงคุณค่า
  • กองซ้อนที่เป็นไปได้ (อาจลงทะเบียนด้วยซ้ำ?)
  • หลีกเลี่ยงการไม่อ้อมค้อม

สิ่งนี้หมายความว่าอย่างไรในกรณีรอ? ดีจริง ... ไม่มีอะไร มีช่วงเวลาสั้น ๆ เท่านั้นที่เครื่องสถานะอยู่บนสแต็ก - จำไว้ว่าawaitทำ a อย่างมีประสิทธิภาพreturnดังนั้นเมธอดสแต็กจึงตาย เครื่องของรัฐจะต้องถูกเก็บรักษาไว้ที่ไหนสักแห่งและ "ที่ไหนสักแห่ง" ก็อยู่บนกอง อายุการใช้งานของกองซ้อนไม่พอดีกับรหัสอะซิงโครนัส :)

นอกเหนือจากนี้เครื่องของรัฐยังละเมิดหลักเกณฑ์ที่ดีในการกำหนดโครงสร้าง:

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

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

จากทั้งหมดนี้แนวทางของคลาสจึงสะอาดกว่าอย่างแน่นอนและฉันไม่คาดหวังว่าประสิทธิภาพจะเพิ่มขึ้นอย่างเห็นได้ชัดจากการใช้ a structแทน วัตถุทั้งหมดที่เกี่ยวข้องมีอายุการใช้งานที่ใกล้เคียงกันดังนั้นวิธีเดียวที่จะปรับปรุงประสิทธิภาพของหน่วยความจำคือการทำให้วัตถุทั้งหมดนั้นstructเป็นไปได้ (เช่นเก็บไว้ในบัฟเฟอร์บางส่วน) ซึ่งเป็นไปไม่ได้ในกรณีทั่วไปแน่นอน และกรณีส่วนใหญ่ที่คุณใช้awaitในตอนแรก (นั่นคืองาน I / O แบบอะซิงโครนัสบางส่วน) เกี่ยวข้องกับคลาสอื่น ๆ อยู่แล้วเช่นบัฟเฟอร์ข้อมูลสตริง ... ไม่น่าเป็นไปได้ที่คุณจะทำawaitสิ่งที่ส่งคืน42โดยไม่ทำอะไรเลย การจัดสรรฮีป

ในท้ายที่สุดฉันจะบอกว่าที่เดียวที่คุณจะเห็นความแตกต่างของประสิทธิภาพที่แท้จริงคือเกณฑ์มาตรฐาน และการเพิ่มประสิทธิภาพสำหรับการวัดประสิทธิภาพเป็นความคิดที่งี่เง่าพูดน้อย ...


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

3
@Damien_The_Unbeliever ใช่นั่นเป็นการค้นพบที่ยอดเยี่ยมอย่างแน่นอนฉันเพิ่มคะแนนคำตอบของคุณแล้ว: P
Luaan

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