ทำการดำเนินการแบบอะซิงโครนัสใน ASP.NET MVC ใช้เธรดจาก ThreadPool บน. NET 4


158

หลังจากคำถามนี้ทำให้ฉันรู้สึกสบายใจเมื่อใช้การดำเนินการ async ใน ASP.NET MVC ดังนั้นฉันจึงเขียนบล็อกโพสต์สองข้อความว่า:

ฉันมีความเข้าใจผิดในใจเกี่ยวกับการทำงานแบบอะซิงโครนัสบน ASP.NET MVC มากเกินไป

ฉันมักจะได้ยินประโยคนี้: แอปพลิเคชันสามารถปรับขนาดได้ดีขึ้นหากการดำเนินการแบบอะซิงโครนัส

และฉันก็ได้ยินประโยคแบบนี้มากมายเช่นกัน: ถ้าคุณมีปริมาณการใช้ข้อมูลจำนวนมากคุณอาจจะดีกว่าถ้าคุณไม่ตอบแบบสอบถามแบบอะซิงโครนัสโดยใช้เธรดพิเศษ 2 เธรดในการให้บริการหนึ่งคำขอ

ฉันคิดว่าทั้งสองประโยคนั้นไม่สอดคล้องกัน

ฉันไม่มีข้อมูลมากนักเกี่ยวกับการทำงานของ threadpool บน ASP.NET แต่ฉันรู้ว่า threadpool นั้นมีขนาดที่ จำกัด สำหรับเธรด ดังนั้นประโยคที่สองจะต้องเกี่ยวข้องกับปัญหานี้

และฉันต้องการทราบว่าการดำเนินการแบบอะซิงโครนัสใน ASP.NET MVC ใช้เธรดจาก ThreadPool บน. NET 4 หรือไม่

ตัวอย่างเช่นเมื่อเราใช้ AsyncController โครงสร้างของแอพจะเป็นอย่างไร? ถ้าฉันได้รับปริมาณการใช้งานมากเป็นความคิดที่ดีที่จะใช้ AsyncController

มีใครบ้างไหมที่สามารถดึงม่านสีดำนี้ออกไปต่อหน้าต่อตาฉันและอธิบายข้อตกลงเกี่ยวกับ asynchrony บน ASP.NET MVC 3 (NET 4)

แก้ไข:

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

การใช้ตัวควบคุมแบบอะซิงโครนัสใน ASP.NET MVC

แก้ไข:

สมมติว่าฉันมีการกระทำของคอนโทรลเลอร์ดังด้านล่าง (ไม่ใช่การนำไปปฏิบัติAsyncController)

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

อย่างที่คุณเห็นที่นี่ฉันทำการผ่าตัดและลืมมันไป จากนั้นฉันกลับมาทันทีโดยไม่ต้องรอให้เสร็จ

ในกรณีนี้สิ่งนี้ต้องใช้เธรดจาก threadpool หรือไม่? หากเป็นเช่นนั้นหลังจากเสร็จสิ้นแล้วจะเกิดอะไรขึ้นกับชุดข้อความนั้น เข้าGCมาและทำความสะอาดหลังจากเสร็จสิ้นหรือไม่

แก้ไข:

สำหรับคำตอบ @ Darin นี่คือตัวอย่างของรหัส async ที่พูดถึงฐานข้อมูล:

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}

ไม่แน่ใจในคำตอบ แต่สิ่งที่น่าสังเกตก็คืออะซิงโครนัสและมัลติเธรดเป็นสิ่งที่แตกต่างกัน ดังนั้นจึงเป็นไปได้ที่จะมีจำนวนเธรดที่แน่นอนที่มีการจัดการแบบอะซิงโครนัส สิ่งที่จะเกิดขึ้นคือเมื่อหน้าหนึ่งต้องปิดกั้นการพูด I / O หน้าอื่นจะได้รับโอกาสทำงานบนเธรดเดียวกัน ซึ่งเป็นวิธีที่ทั้งสองคำสั่งเหล่านั้นสามารถเป็นจริง async สามารถทำให้สิ่งต่าง ๆ เร็วขึ้น แต่เธรดจำนวนมากเกินไปเป็นปัญหา
Chris Chilvers

@ChrisChilvers ใช่ไม่จำเป็นต้องใช้มัลติเธรดในการดำเนินการแบบอะซิงโครนัส ฉันคิดแล้วว่า แต่ฉันคิดว่าฉันไม่มีผู้ควบคุมมากกว่าที่ฉันสามารถบอกได้ AsyncController หมุนจำนวนกระทู้ที่มันต้องการจากมุมมองของฉัน แต่ไม่แน่ใจเหมือนกัน มีความคิดเกี่ยวกับ threadpool บนแอพเดสก์ท็อปอย่าง WPF หรือไม่? ฉันคิดว่าจำนวนกระทู้ไม่ได้มีปัญหากับแอพประเภทนั้น
tugberk

6
ดูวิดีโอการทำเกลียวกับ Jeff Richter
oleksii

ฉันคิดว่าปัญหา (และไม่สอดคล้องกัน) จึงเป็นคำสั่งที่สองใช้ asynchronous เมื่อมันหมายถึงหัวข้อจำนวนมาก อาจเป็นเพราะนั่นคือวิธีที่ asp.net ใช้งานหน้า async และการใช้งานเฉพาะทำให้เกิดปัญหาสับสน (เนื่องจากชื่อของคุณลักษณะที่ทำให้เกิดปัญหาจะเป็นหน้า async) แต่ฉันไม่แน่ใจเกี่ยวกับการดำเนินการเฉพาะ . ดังนั้นพวกเขาอาจหมายถึง "กระทู้จำนวนมาก" หรือพวกเขาหมายถึง "หน้า async ภายใน asp.net รุ่น X" เนื่องจากรุ่นในอนาคตอาจเปลี่ยนการใช้งาน หรืออาจหมายถึงการใช้เธรดพูลเพื่อดำเนินการ async ภายในเพจ
Chris Chilvers

@ChrisChilvers โอ้มนุษย์! ฉันสับสนมากขึ้นหลังจากความคิดเห็นเหล่านี้: s
tugberk

คำตอบ:


177

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

ก่อนอื่นให้พิจารณาการกระทำแบบซิงโครนัสมาตรฐาน:

public ActionResult Index()
{
    // some processing
    return View();
}

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

ตอนนี้ลองมาเป็นตัวอย่างของรูปแบบอะซิงโครนัส:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

เมื่อคำร้องขอถูกส่งไปยังการกระทำดัชนีเธรดจะถูกดึงออกจากเธรดพูลและเนื้อความของIndexAsyncเมธอดจะถูกดำเนินการ เมื่อเนื้อความของวิธีนี้เสร็จสิ้นการดำเนินการเธรดจะถูกส่งกลับไปยังกลุ่มเธรด จากนั้นใช้มาตรฐานAsyncManager.OutstandingOperationsเมื่อคุณส่งสัญญาณการดำเนินการ async เสร็จสมบูรณ์เธรดอื่นจะถูกดึงออกมาจากเธรดพูลและเนื้อความของการIndexCompletedดำเนินการจะถูกดำเนินการบนและผลลัพธ์ที่แสดงไปยังไคลเอ็นต์

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

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

ดังนั้นเมื่อใดที่เราสามารถใช้ประโยชน์จากคอนโทรลเลอร์แบบอะซิงโครนัสได้อย่างแท้จริงคุณอาจถาม?

IMHO เราสามารถได้รับประโยชน์สูงสุดเมื่อเรามีการใช้งาน I / O อย่างเข้มข้น (เช่นการโทรฐานข้อมูลและเครือข่ายไปยังบริการระยะไกล) หากคุณมีการใช้งาน CPU มากการกระทำแบบอะซิงโครนัสจะไม่ก่อให้เกิดประโยชน์มากนัก

เหตุใดเราจึงได้รับประโยชน์จากการดำเนินการที่เข้มข้นของ I / O เพราะเราสามารถใช้พอร์ตที่สมบูรณ์ของ I / Oได้ IOCP มีประสิทธิภาพสูงมากเนื่องจากคุณไม่ได้ใช้เธรดหรือทรัพยากรใด ๆ บนเซิร์ฟเวอร์ในระหว่างการดำเนินการทั้งหมด

พวกเขาทำงานอย่างไร

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

เช่นเดียวกันกับวิธีการเช่น FileStream.BeginRead, SqlCommand.BeginExecute, ...

สิ่งที่เกี่ยวกับการขนานฐานข้อมูลหลายสาย? สมมติว่าคุณมีตัวควบคุมแบบซิงโครนัสซึ่งคุณทำการบล็อกการเรียกฐานข้อมูล 4 ครั้งตามลำดับ เป็นเรื่องง่ายที่จะคำนวณว่าถ้าการเรียกฐานข้อมูลแต่ละครั้งใช้เวลา 200ms การกระทำของคอนโทรลเลอร์จะใช้เวลาประมาณ 800ms ในการดำเนินการ

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

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

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

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

ลองพิจารณาตัวอย่างของคุณ:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

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

ตอนนี้มาลองพิจารณาสิ่งต่อไปนี้:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

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


1
รายละเอียดจริงๆขอบคุณ! สมมติว่าเรามี 4 งาน async ( System.Threading.Task) ที่ทำงานอยู่ภายในIndexAsyncวิธี ในการดำเนินการเหล่านั้นเรากำลังเรียก db ไปยังเซิร์ฟเวอร์ ดังนั้นพวกเขาทั้งหมดคือการดำเนินการ I / O ที่เข้มข้นใช่มั้ย ในกรณีดังกล่าวเราจะสร้าง 4 เธรดแยกกัน (หรือรับ 4 เธรดแยกจากเธรดพูล) หรือไม่ สมมติว่าฉันมีเครื่องหลายคอร์ที่พวกเขากำลังทำงานแบบขนานเช่นกันใช่ไหม?
tugberk

10
@tugberk การเรียกใช้ฐานข้อมูลเป็นการดำเนินงานของ I / O แต่ทั้งหมดจะขึ้นอยู่กับวิธีการใช้งาน หากคุณใช้การโทรฐานข้อมูลการปิดกั้นเช่นSqlCommand.ExecuteReaderคุณกำลังสูญเสียทุกอย่างตั้งแต่นี้เป็นการโทรปิดกั้น คุณกำลังบล็อกเธรดที่การเรียกนี้ดำเนินการและถ้าเธรดนี้เกิดขึ้นเป็นเธรดจากพูลมันแย่มาก คุณจะได้รับประโยชน์เท่านั้นถ้าคุณใช้ I / O SqlCommand.BeginExecuteReaderพอร์ตแล้วเสร็จ: หากคุณไม่ได้ใช้ IOCP ไม่ว่าคุณจะทำอะไรอย่าใช้ตัวควบคุม async เพราะคุณจะได้รับความเสียหายมากกว่าผลประโยชน์จากประสิทธิภาพโดยรวมของแอปพลิเคชันของคุณ
ดารินทร์ดิมิทรอฟ

1
ส่วนใหญ่ฉันใช้รหัส EF ก่อน ฉันไม่แน่ใจว่าเหมาะสมหรือไม่ ฉันวางตัวอย่างซึ่งแสดงสิ่งที่ฉันทำโดยทั่วไป ฉันอัปเดตคำถามคุณสามารถดูได้ไหม
tugberk

3
@tugberk คุณกำลังเรียกใช้ขนานดังนั้นเวลารวมของการดำเนินการจะน้อยกว่าเมื่อเทียบกับถ้าคุณเรียกใช้ตามลำดับ แต่ในการใช้งานคุณต้องใช้เธรดผู้ปฏิบัติงาน ที่จริงแล้ว EF ขี้เกียจดังนั้นเมื่อคุณทำเช่นนั้น_context.Fooคุณไม่ได้ทำอะไรเลย คุณเป็นเพียงการสร้างต้นไม้การแสดงออก ระมัดระวังเป็นอย่างยิ่งกับสิ่งนี้ การดำเนินการแบบสอบถามจะถูกเลื่อนออกไปเฉพาะเมื่อคุณเริ่มนับผ่านชุดผลลัพธ์ และหากสิ่งนี้เกิดขึ้นในมุมมองอาจเป็นความหายนะต่อประสิทธิภาพ เมื่อต้องการดำเนินการแบบสอบถามแบบ EF เพิ่มผนวก.ToList()ท้าย
ดารินทร์ดิมิทรอฟ

2
@tugberk คุณจะต้องใช้เครื่องมือทดสอบโหลดเพื่อจำลองผู้ใช้หลายคนพร้อมกันบนเว็บไซต์ของคุณเพื่อดูว่ามันทำงานอย่างไรภายใต้ภาระหนัก Mini Profiler ไม่สามารถจำลองการโหลดบนเว็บไซต์ของคุณ มันอาจช่วยให้คุณเห็นและเพิ่มประสิทธิภาพการสืบค้น ADO.NET และโปรไฟล์คำขอเดียวซึ่งไร้ประโยชน์เมื่อคุณต้องการดูว่าเว็บไซต์ของคุณทำงานอย่างไรในสถานการณ์จริงเมื่อผู้ใช้จำนวนมากกำลังกดปุ่ม
ดารินทร์ดิมิทรอฟ

49

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

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

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

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

หากไม่มีตัวควบคุม async ผู้โดยสารที่อยู่ด้านหลังคุณจะต้องรออายุขณะที่คุณมองหาเงินของคุณในขณะที่คนขับรถบัสจะไม่ทำงาน

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


8
การเปรียบเทียบที่ดีมาก ขอบคุณ.
Pittsburgh DBA

ฉันชอบคำอธิบาย ขอบคุณ
Omer Cansizoglu

วิธีที่ยอดเยี่ยมในการอธิบายขอขอบคุณ
Anand Vyas

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

นี่คือการเปรียบเทียบที่ดี แต่คำถามเดียวของฉันคือ: คนที่อึกอักในกระเป๋าของเขาในการเปรียบเทียบของคุณจะเป็นงาน / การประมวลผลในแอปพลิเคชันของเรา ... ดังนั้นกระบวนการที่ทำงานเมื่อเราปล่อยเธรด? แน่นอนมันเป็นหัวข้ออื่นหรือไม่ แล้วเราจะได้อะไรที่นี่?
Tomuke

10

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

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

เป็นของประเภทที่สอง ผู้ใช้จะได้รับการตอบสนองที่เร็วขึ้น แต่ปริมาณงานทั้งหมดบนเซิร์ฟเวอร์จะสูงขึ้นเนื่องจากต้องทำงาน + เดียวกันกับจัดการเธรด

ตัวอย่างของสิ่งนี้จะเป็น:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

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

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

ตัวควบคุม Async ใน MVC มีเป้าหมายอื่นแม้ว่า จุดที่นี่คือการหลีกเลี่ยงการมีส่วนร่วมของเธรดรอบ ๆ ทำอะไร (ซึ่งอาจเจ็บ scalability) เป็นเรื่องสำคัญหาก API ที่คุณโทรหามีวิธีการแบบอะซิงก์เท่านั้น ชอบ WebClient.DowloadStringAsync ()

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

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


6

การประยุกต์ใช้งานสามารถปรับขนาดการดำเนินงานดีกว่าถ้าทำงานแบบไม่พร้อมกัน แต่เฉพาะในกรณีที่มีทรัพยากรที่มีอยู่เพื่อให้บริการการดำเนินงานเพิ่มเติม

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

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

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

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

แก้ไข:

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

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


ขอบคุณ! หลังจากที่ฉันอ่านคำตอบของคุณฉันแก้ไขคำถามด้วยตัวอย่างโค้ด คุณได้ดูไหม
tugberk

คุณมีประโยควิเศษสำหรับฉัน: การTask.Factory.StartNewโทรจะจัดคิวการดำเนินการบน. NET เธรดพูล . ในบริบทนี้อันที่ถูกต้องที่นี่: 1-)มันจะสร้างเธรดใหม่และเมื่อเสร็จแล้วเธรดนั้นจะกลับไปที่เธรดพูลและรอให้มีการใช้ซ้ำอีกครั้ง 2-)ได้รับเธรดจาก threadpool และเธรดนั้นกลับไปที่เธรดพูลและรอให้มีการใช้ซ้ำอีกครั้ง 3-)ใช้แนวทางที่มีประสิทธิภาพมากที่สุดและสามารถทำได้ทั้งสองวิธี
tugberk

1
เธรดพูลสร้างเธรดตามที่จำเป็นและรีไซเคิลเธรดเมื่อไม่ได้ใช้งาน พฤติกรรมที่แน่นอนมีความหลากหลายในเวอร์ชัน CLR คุณสามารถค้นหาข้อมูลเฉพาะได้ที่นี่msdn.microsoft.com/en-us/library/0ka9477y.aspx
Paul Turner

ตอนนี้ฉันเริ่มคิดขึ้นแล้ว ดังนั้น CLR จึงเป็นเจ้าของเธรดพูลใช่ไหม ตัวอย่างเช่นแอ็พพลิเคชัน WPF ยังมีความคิดของ thread-pool และมันเกี่ยวข้องกับ
tugberk

1
Thread Pool เป็นของตัวเองภายใน CLR ส่วนประกอบอื่น ๆ ที่ "รับรู้" ของพูลกำลังระบุว่าพวกเขาใช้เธรดกลุ่มเธรด (ตามความเหมาะสม) แทนที่จะสร้างและทำลายชุดของตนเอง การสร้างหรือทำลายเธรดเป็นการดำเนินการที่ค่อนข้างแพงดังนั้นการใช้พูลจึงเป็นการเพิ่มประสิทธิภาพอย่างมากสำหรับการดำเนินการแบบสั้น ๆ
Paul Turner

2

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

http://msdn.microsoft.com/en-us/library/ee728598.aspx

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

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


ฉันได้อ่านเอกสารนั้นหลายร้อยครั้งและฉันก็ยังมีความสับสนอยู่มาก (อาจเป็นปัญหาก็คือฉันใครจะรู้) เมื่อคุณมองไปรอบ ๆ คุณจะเห็นความคิดเห็นที่ไม่สอดคล้องกันมากเกี่ยวกับอะซิงโครนัสบน ASP.NET MVC อย่างที่คุณเห็นในคำถามของฉัน
tugberk

สำหรับประโยคสุดท้าย: ภายในแอคชั่นคอนโทรลเลอร์ฉันกำลังสืบค้นฐานข้อมูล 5 ครั้งแยกกัน (ฉันต้อง) และมันใช้เวลาประมาณ 400 มิลลิวินาที จากนั้นฉันก็ใช้ AsyncController และรันมันแบบขนาน เวลาตอบสนองลดลงอย่างมากเหลือเพียงประมาณ 200 ms แต่ฉันไม่รู้ว่าจะสร้างเธรดจำนวนเท่าใดจะเกิดอะไรขึ้นกับเธรดเหล่านั้นหลังจากฉันทำเสร็จแล้วจะGCมาและทำความสะอาดทันทีหลังจากฉันทำเสร็จแล้วเพื่อให้แอปของฉันไม่มีหน่วยความจำรั่วไหลดังนั้นต่อไป ความคิดใด ๆ ในส่วนนั้น
tugberk

แนบดีบักเกอร์และค้นหา
AR

0

สิ่งแรกไม่ใช่ MVC แต่ IIS ที่ดูแลเธรดพูล ดังนั้นการร้องขอใด ๆ ที่มาถึงแอปพลิเคชัน MVC หรือ ASP.NET จะถูกส่งจากเธรดซึ่งเก็บรักษาไว้ในเธรดพูล เฉพาะการสร้างแอป Asynch เขาเรียกใช้การกระทำนี้ในเธรดอื่นและปล่อยเธรดทันทีเพื่อให้สามารถดำเนินการตามคำขออื่นได้

ฉันได้อธิบายแบบเดียวกันกับวิดีโอรายละเอียด ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "ตัวควบคุม Asynch MVC และความอดอยากของเธรด") ซึ่งแสดงให้เห็นว่าการอดอาหารในเธรดเกิดขึ้นใน MVC อย่างไรและย่อเล็กสุดเพียงใดโดยใช้ MVC Asynch controllers.I ยังวัดคิวการร้องขอโดยใช้ perfmon เพื่อให้คุณสามารถดูว่าการร้องขอคิวจะลดลงสำหรับ MVC asynch และวิธีที่แย่ที่สุดสำหรับการดำเนินการ Synch

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