เหตุใดฉันจึงควรสร้างการดำเนินการ async WebAPI แทนการซิงค์


109

ฉันมีการดำเนินการต่อไปนี้ใน Web API ที่ฉันสร้างขึ้น:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

การเรียกใช้บริการเว็บนี้ทำผ่าน Jquery Ajax เรียกวิธีนี้:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

ฉันเคยเห็นนักพัฒนาบางคนที่ใช้การดำเนินการก่อนหน้านี้ด้วยวิธีนี้:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

ต้องบอกว่า GetProductsWithHistory () เป็นการดำเนินการที่ค่อนข้างยาว จากปัญหาและบริบทของฉันการทำให้การทำงานแบบอะซิงโครนัส webAPI เป็นประโยชน์ต่อฉันอย่างไร


1
ฝั่งไคลเอ็นต์ใช้ AJAX ซึ่งเป็นแบบอะซิงโครนัสอยู่แล้ว คุณไม่จำเป็นต้องให้บริการเขียนเป็นasync Task<T>ไฟล์. โปรดจำไว้ว่า AJAX ถูกนำมาใช้ก่อนที่ TPL จะมีอยู่จริง :)
Dominic Zukiewicz

65
คุณต้องเข้าใจว่าเหตุใดคุณจึงใช้ตัวควบคุม async หลายคนไม่ทำเช่นนั้น IIS มีเธรดจำนวน จำกัด และเมื่อใช้งานทั้งหมดเซิร์ฟเวอร์จะไม่สามารถประมวลผลคำขอใหม่ได้ เมื่อใช้ตัวควบคุม async เมื่อกระบวนการกำลังรอให้ I / O เสร็จสิ้นเธรดของมันจะถูกปล่อยให้เซิร์ฟเวอร์ใช้สำหรับการประมวลผลคำร้องขออื่น ๆ
Matija Grcic

3
คุณเคยเห็นนักพัฒนาคนไหนทำแบบนั้นบ้าง? หากมีบล็อกโพสต์หรือบทความที่แนะนำเทคนิคนั้นโปรดโพสต์ลิงก์
Stephen Cleary

3
คุณจะได้รับประโยชน์เต็มที่จาก async ก็ต่อเมื่อกระบวนการของคุณมีการรับรู้แบบ async จากด้านบน (รวมถึงเว็บแอปพลิเคชันและตัวควบคุมของคุณ) จนถึงกิจกรรมที่รอคอยที่เกิดขึ้นนอกกระบวนการของคุณ (รวมถึงความล่าช้าของตัวจับเวลา, ไฟล์ I / O, การเข้าถึงฐานข้อมูล, และคำขอทางเว็บ) ในกรณีนี้ผู้ช่วยผู้รับมอบสิทธิ์ของคุณต้องการกลับมาGetProductsWithHistoryAsync() Task<CartTotalsDTO>การเขียนตัวควบคุม async ของคุณอาจมีประโยชน์หากคุณต้องการย้ายการเรียกที่ทำให้เป็น async ด้วย จากนั้นคุณจะเริ่มได้รับประโยชน์จากส่วน async เมื่อคุณโยกย้ายส่วนที่เหลือ
Keith Robertson

1
หากกระบวนการที่คุณทำอยู่เกิดขึ้นและไปที่ฐานข้อมูลแสดงว่าเธรดเว็บของคุณกำลังรอให้มันกลับมาและถือเธรดนั้น หากคุณมีจำนวนเธรดสูงสุดและมีคำขออื่นเข้ามาก็ต้องรอ ทำไมทำอย่างนั้น? แต่คุณต้องการปลดปล่อยเธรดนั้นจากคอนโทรลเลอร์ของคุณเพื่อให้คำขออื่นใช้งานได้และใช้เธรดเว็บอื่นเมื่อคำขอเดิมของคุณจากฐานข้อมูลกลับมาเท่านั้น msdn.microsoft.com/en-us/magazine/dn802603.aspx
user441521

คำตอบ:


98

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

ในการสนทนาของฉันเกี่ยวกับ "async over sync" ฉันขอแนะนำอย่างยิ่งว่าหากคุณมี API ที่ติดตั้งภายในแบบซิงโครนัสคุณไม่ควรเปิดเผยคู่อะซิงโครนัสที่รวมวิธีซิงโครนัสเข้าTask.Runด้วยกัน

จากฉันควรเปิดเผยการห่อแบบซิงโครนัสสำหรับวิธีการอะซิงโครนัสหรือไม่

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


3
@efaruk เธรดทั้งหมดเป็นเธรดของผู้ปฏิบัติงาน การปล่อยเธรด ThreadPool หนึ่งเธรดและการบล็อกอีกเธรดนั้นไม่มีจุดหมาย
i3arnon

1
@efaruk ฉันไม่แน่ใจว่าคุณพยายามจะพูดอะไร .. แต่ตราบใดที่คุณยอมรับว่าไม่มีเหตุผลที่จะใช้ async over sync ใน WebAPI ก็ไม่เป็นไร
i3arnon

@efaruk "async over sync" (เช่นawait Task.Run(() => CPUIntensive())) ไม่มีประโยชน์ใน asp.net คุณไม่ได้รับอะไรจากการทำเช่นนั้น คุณเพิ่งปล่อยเธรด ThreadPool หนึ่งเธรดเพื่อครอบครองเธรดอื่น มีประสิทธิภาพน้อยกว่าการเรียกวิธีซิงโครนัส
i3arnon

1
@efaruk ไม่นั่นไม่สมเหตุสมผล ตัวอย่างคุณรันงานอิสระตามลำดับ คุณจำเป็นต้องอ่าน asyc / await ก่อนที่จะให้คำแนะนำ คุณจะต้องใช้await Task.WhenAllเพื่อดำเนินการควบคู่กัน
Søren Boisen

1
@efaruk ดังที่ Boisen อธิบายตัวอย่างของคุณไม่ได้เพิ่มค่าใด ๆ นอกจากการเรียกวิธีการซิงโครนัสเหล่านี้ตามลำดับ คุณสามารถใช้Task.Runถ้าคุณต้องการโหลดแบบขนานกับหลายเธรด แต่นั่นไม่ใช่ความหมายของ "async over sync" การอ้างอิง "async over sync" สร้างเมธอด async เป็น wrapper ทับด้วยซิงโครนัส คุณสามารถดูคำพูดในคำตอบของฉันได้
i3arnon

1

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

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

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.