Async / await vs BackgroundWorker


162

ในไม่กี่วันที่ผ่านมาฉันได้ทดสอบคุณสมบัติใหม่ของ. net 4.5 และ c # 5

ฉันชอบคุณสมบัติใหม่ของ async / await ก่อนหน้านี้ฉันใช้BackgroundWorkerเพื่อจัดการกระบวนการที่ยาวนานขึ้นในพื้นหลังด้วย UI ที่ตอบสนอง

คำถามของฉันคือ: หลังจากมีคุณสมบัติใหม่ที่ดีเหล่านี้ฉันควรใช้ async / คอยและเมื่อเป็นBackgroundWorker ? สถานการณ์ทั่วไปสำหรับทั้งคู่คืออะไร



ทั้งสองเป็นสิ่งที่ดี แต่ถ้าคุณทำงานกับรหัสเก่าที่ไม่ได้ย้ายไปยังรุ่น. net ในภายหลัง; BackgroundWorker ทำงานได้ทั้งสองอย่าง
dcarl661

คำตอบ:


74

async / BackgroundWorkerรอคอยถูกออกแบบมาเพื่อแทนที่โครงสร้างเช่น ในขณะที่คุณสามารถใช้งานได้อย่างแน่นอนหากคุณต้องการคุณควรใช้ async / await พร้อมด้วยเครื่องมือ TPL อื่น ๆ เพื่อจัดการกับทุกสิ่งที่อยู่ข้างนอก

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


17
ขอบคุณ สำหรับฉัน async / คอยดูเหมือนชัดเจนมากขึ้นและ 'ธรรมชาติ' BakcgoundWorker ทำให้รหัส 'มีเสียงดัง' ในความคิดของฉัน
ทอม

12
@ Tom ดีที่เป็นเหตุผลที่ไมโครซอฟท์ใช้เวลามากเวลาและความพยายามดำเนินการนั้น ถ้ามันไม่ได้ดีไปกว่านี้พวกเขาก็จะไม่ใส่ใจ
Servy

5
ใช่. สิ่งที่รอคอยใหม่ทำให้ BackgroundWorker เก่าดูด้อยกว่าและล้าสมัยไปอย่างสิ้นเชิง ความแตกต่างนั้นน่าทึ่งมาก
usr

16
ฉันมีบทสรุปที่ค่อนข้างดีในบล็อกของฉันเมื่อเปรียบเทียบวิธีการต่าง ๆ กับงานพื้นหลัง โปรดทราบว่าasync/ awaitยังอนุญาตการเขียนโปรแกรมแบบอะซิงโครนัสโดยไม่มีเธรดพูลเธรด
Stephen Cleary

8
ลงคำตอบนี้มันทำให้เข้าใจผิด Async / await ไม่ได้ถูกออกแบบมาเพื่อแทนที่ผู้ทำงานเบื้องหลัง
Quango

206

นี่น่าจะเป็น TL; DR สำหรับหลาย ๆ คน แต่ฉันคิดว่าการเปรียบเทียบawaitกับBackgroundWorkerนั้นเหมือนกับการเปรียบเทียบแอปเปิ้ลและส้มและความคิดของฉันเกี่ยวกับสิ่งต่อไปนี้:

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

ตัวอย่างเช่นคุณสามารถทำสิ่งต่อไปนี้ด้วยawait:

using (WebResponse response = await webReq.GetResponseAsync())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        int bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);
    }
}

แต่คุณอาจไม่เคยทำแบบนั้นในคนทำงานเบื้องหลังคุณอาจทำสิ่งนี้ใน. NET 4.0 (ก่อนหน้าawait):

webReq.BeginGetResponse(ar =>
{
    WebResponse response = webReq.EndGetResponse(ar);
    Stream responseStream = response.GetResponseStream();
    responseStream.BeginRead(buffer, 0, buffer.Length, ar2 =>
    {
        int bytesRead = responseStream.EndRead(ar2);
        responseStream.Dispose();
        ((IDisposable) response).Dispose();
    }, null);
}, null);

ขอให้สังเกต disjointness ของการกำจัดเมื่อเทียบระหว่างสองไวยากรณ์และวิธีการที่คุณไม่สามารถใช้usingโดยไม่ต้อง/asyncawait

BackgroundWorkerแต่คุณจะไม่ทำอะไรแบบนั้นด้วย BackgroundWorkerโดยปกติจะใช้สำหรับการสร้างแบบจำลองการดำเนินการที่ยาวนานซึ่งคุณไม่ต้องการส่งผลกระทบต่อการตอบสนองของ UI ตัวอย่างเช่น:

worker.DoWork += (sender, e) =>
                    {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                    };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // TODO: do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

ไม่มีอะไรจริงๆที่คุณสามารถใช้ async / รอด้วยBackgroundWorkerกำลังสร้างเธรดให้คุณ

ตอนนี้คุณสามารถใช้ TPL แทน:

var synchronizationContext = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
                      {
                        int i = 0;
                        // simulate lengthy operation
                        Stopwatch sw = Stopwatch.StartNew();
                        while (sw.Elapsed.TotalSeconds < 1)
                            ++i;
                      }).ContinueWith(t=>
                                      {
                                        // TODO: do something on the UI thread, like
                                        // update status or display "result"
                                      }, synchronizationContext);

ในกรณีนี้TaskSchedulerคือการสร้างเธรดสำหรับคุณ (สมมติว่าเป็นค่าเริ่มต้นTaskScheduler) และสามารถใช้awaitดังนี้:

await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                        ++i;
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

ในความเห็นของฉันการเปรียบเทียบที่สำคัญคือคุณกำลังรายงานความคืบหน้าหรือไม่ ตัวอย่างเช่นคุณอาจมีสิ่งBackgroundWorker likeนี้:

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (sender, eventArgs) =>
                            {
                            // TODO: something with progress, like update progress bar

                            };
worker.DoWork += (sender, e) =>
                 {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                            ((BackgroundWorker)sender).ReportProgress((int) (1000 / sw.ElapsedMilliseconds));
                        ++i;
                    }
                 };
worker.RunWorkerCompleted += (sender, eventArgs) =>
                                {
                                    // do something on the UI thread, like
                                    // update status or display "result"
                                };
worker.RunWorkerAsync();

แต่คุณจะไม่จัดการกับสิ่งนี้เพราะคุณต้องลากและวางส่วนประกอบของผู้ทำงานเบื้องหลังลงบนพื้นผิวการออกแบบของฟอร์ม - สิ่งที่คุณไม่สามารถทำได้ด้วยasync/ awaitและTask... เช่นคุณชนะ ' ไม่สร้างวัตถุด้วยตนเองตั้งค่าคุณสมบัติและตั้งค่าตัวจัดการเหตุการณ์ คุณต้องการเพียงกรอกในร่างกายของDoWork, RunWorkerCompletedและProgressChangedจัดการเหตุการณ์

หากคุณ "แปลง" เป็น async / await คุณจะทำสิ่งต่อไปนี้:

     IProgress<int> progress = new Progress<int>();

     progress.ProgressChanged += ( s, e ) =>
        {
           // TODO: do something with e.ProgressPercentage
           // like update progress bar
        };

     await Task.Factory.StartNew(() =>
                  {
                    int i = 0;
                    // simulate lengthy operation
                    Stopwatch sw = Stopwatch.StartNew();
                    while (sw.Elapsed.TotalSeconds < 1)
                    {
                        if ((sw.Elapsed.TotalMilliseconds%100) == 0)
                        {
                            progress.Report((int) (1000 / sw.ElapsedMilliseconds))
                        }
                        ++i;
                    }
                  });
// TODO: do something on the UI thread, like
// update status or display "result"

หากไม่มีความสามารถในการลากส่วนประกอบไปยังพื้นผิว Designer มันขึ้นอยู่กับผู้อ่านว่าจะเลือก "ดี" จริงหรือไม่ แต่สำหรับฉันแล้วการเปรียบเทียบระหว่างawaitและBackgroundWorkerไม่ว่าคุณจะสามารถรอวิธีการในตัวStream.ReadAsyncได้หรือไม่ เช่นถ้าคุณใช้ตามที่ตั้งใจไว้ก็อาจจะยากที่จะเปลี่ยนไปใช้BackgroundWorkerawait

ความคิดอื่น ๆ : http://jeremybytes.blogspot.ca/2012/05/backgroundworker-component-im-not-dead.html


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

4
@Moozhe var t1 = webReq.GetResponseAsync(); var t2 = webReq2.GetResponseAsync(); await t1; await t2;ไม่เป็นความจริงที่คุณสามารถทำได้ ซึ่งจะรอการดำเนินการสองขนาน รอจะดีสำหรับตรงกัน แต่งานลำดับ IMO ...
ปีเตอร์ริตชี่

2
@Moozhe ใช่ทำอย่างนั้นรักษาลำดับ - ตามที่ฉันกล่าวถึง นี่คือจุดหลักของการรอคอยคือการได้รับ asynchronisity ในรหัสที่ดูเป็นลำดับ แน่นอนคุณสามารถใช้await Task.WhenAny(t1, t2)เพื่อทำอะไรบางอย่างเมื่องานเสร็จก่อน คุณอาจต้องการวนซ้ำเพื่อให้แน่ใจว่างานอื่นจะเสร็จสมบูรณ์เช่นกัน โดยปกติแล้วคุณต้องการที่จะรู้ว่าเมื่อเฉพาะเสร็จงานซึ่งจะนำคุณไปเขียนตามลำดับawaits
Peter Ritchie


5
ความซื่อสัตย์สุจริต BackgroundWorker ไม่เคยดีสำหรับการดำเนินงานที่ถูกผูกไว้กับ IO
Peter Ritchie

21

นี่เป็นการแนะนำที่ดี: http://msdn.microsoft.com/en-us/library/hh191443.aspx ส่วนเธรดเป็นเพียงสิ่งที่คุณกำลังมองหา:

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

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

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


"สำหรับการดำเนินการที่ถูกผูกไว้กับ IO เพราะรหัสนั้นง่ายกว่าและคุณไม่ต้องป้องกันสภาวะการแข่งขัน" เงื่อนไขการแข่งขันใดที่สามารถเกิดขึ้นได้คุณสามารถยกตัวอย่างได้หรือไม่?
eran otzap

8

BackgroundWorkerมีการระบุไว้อย่างชัดเจนว่าล้าสมัยใน. NET 4.5:

บทความ MSDN "การเขียนโปรแกรมแบบอะซิงโครนัสกับ Async และ Await (C # และ Visual Basic)"บอก:

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

UPDATE

  • ในการตอบสนองต่อความคิดเห็นของ@ eran-otzap :
    "สำหรับการดำเนินการของ IO เนื่องจากขอบเขตของรหัสนั้นง่ายกว่าและคุณไม่ต้องป้องกันสภาพการแข่งขัน" เงื่อนไขการแข่งขันใดที่คุณสามารถยกตัวอย่าง "

ควรตั้งคำถามนี้เป็นโพสต์แยกต่างหาก

Wikipedia มีคำอธิบายที่ดีเกี่ยวกับสภาพการแข่งขัน ส่วนที่จำเป็นคือมัลติเธรดและจากบทความ MSDN เดียวกันAsynchronous Programming ด้วย Async และ Await (C # และ Visual Basic) :

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

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

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

นั่นคือ "คำหลักแบบ async และการรอคอยจะไม่ทำให้เกิดการสร้างเธรดเพิ่มเติม"

เท่าที่ฉันสามารถจำความพยายามของตัวเองเมื่อฉันกำลังศึกษาบทความนี้เมื่อปีที่แล้วถ้าคุณได้เล่นและเล่นกับตัวอย่างโค้ดจากบทความเดียวกันคุณสามารถชนในสถานการณ์ที่เป็นรุ่นที่ไม่ใช่ async (คุณสามารถแปลง ปิดกั้นตัวคุณเองไปเรื่อย ๆ !

สำหรับตัวอย่างที่เป็นรูปธรรมคุณสามารถค้นหาไซต์นี้ได้ นี่คือตัวอย่าง:


30
BackgrondWorker ไม่ได้ระบุอย่างชัดเจนว่าล้าสมัยใน. NET 4.5 บทความ MSDN เพียงบอกว่าการดำเนินการ IO- ผูกจะดีกว่าด้วยวิธีการ async - การใช้ BackgroundWorker ไม่ได้หมายความว่าคุณไม่สามารถใช้วิธี async
Peter Ritchie

@ PeterRitchie ฉันแก้ไขคำตอบของฉัน สำหรับฉัน "วิธีการที่มีอยู่ล้าสมัย" เป็นคำพ้องกับ "วิธีการแบบอะซิงโครนัสกับการเขียนโปรแกรมแบบอะซิงโครนัสเป็นวิธีที่มีอยู่ในเกือบทุกกรณี"
Gennady Vanin ГеннадийВанин

7
ฉันมีปัญหากับหน้า MSDN นั้น สำหรับหนึ่งคุณไม่ต้อง "ประสานงาน" กับ BGW มากกว่าที่คุณทำกับงาน และใช่ BGW ไม่เคยตั้งใจที่จะดำเนินการโดยตรง operstions IO - there'd เสมอได้เป็นวิธีที่ดีกว่าการทำ IO กว่าใน BGW คำตอบอื่น ๆ แสดงให้เห็นว่า BGW ไม่มีความซับซ้อนในการใช้งานมากกว่างาน และถ้าคุณใช้ BGW อย่างถูกต้องจะไม่มีเงื่อนไขการแข่งขัน
Peter Ritchie

"สำหรับการดำเนินการที่ถูกผูกไว้กับ IO เพราะรหัสนั้นง่ายกว่าและคุณไม่ต้องป้องกันสภาวะการแข่งขัน" เงื่อนไขการแข่งขันใดที่สามารถเกิดขึ้นได้คุณสามารถยกตัวอย่างได้หรือไม่?
eran otzap

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