BackgroundWorker vs กระทู้พื้นหลัง


166

ฉันมีคำถามเกี่ยวกับโวหารเกี่ยวกับการเลือกการใช้เธรดพื้นหลังที่ฉันควรใช้กับแอพพลิเคชั่น windows form ปัจจุบันฉันมีBackgroundWorkerรูปแบบที่มีการ(while(true))วนซ้ำไม่สิ้นสุด ในวงนี้ฉันใช้WaitHandle.WaitAnyเพื่อเก็บกระทู้งีบหลับจนกว่าสิ่งที่น่าสนใจเกิดขึ้น เหตุการณ์หนึ่งที่ฉันจัดการรอเป็นStopThreadเหตุการณ์ "" เพื่อให้ฉันสามารถแยกวง Form.Dispose()เหตุการณ์นี้จะส่งสัญญาณเมื่อจากแทนที่ของฉัน

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

คำตอบ:


88

จากความเข้าใจในคำถามของคุณคุณกำลังใช้BackgroundWorkerเธรดมาตรฐาน

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

เหตุการณ์ต้องการRunWorkerCompletedส่งสัญญาณเมื่อเธรดเสร็จสิ้นสิ่งที่จำเป็นต้องทำและProgressChangedเหตุการณ์เพื่ออัพเดต GUI ในความคืบหน้าของเธรด

ดังนั้นหากคุณไม่ได้ใช้สิ่งเหล่านี้ฉันไม่เห็นอันตรายใด ๆ ในการใช้เธรดมาตรฐานสำหรับสิ่งที่คุณต้องทำ


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

ฉันคิดว่า BackgroundWorker.IsBusy คือสิ่งที่คุณกำลังมองหา
ParmesanCodice

1
ใช้CancelAsync(และทดสอบCancellationPendingว่าเธรดของคุณจะทำการสำรวจในช่วงเวลาสั้น ๆ หรือไม่หากคุณต้องการให้มีการยกข้อยกเว้นแทนให้ใช้การSystem.Threading.Thread.Abort()ยกที่มีข้อยกเว้นภายในบล็อกเธรดเองให้เลือกโมเดลที่เหมาะสมสำหรับสถานการณ์
Brett Ryan

369

บางส่วนของความคิดของฉัน ...

  1. ใช้BackgroundWorkerหากคุณมีงานเดียวที่ทำงานในพื้นหลังและต้องการโต้ตอบกับ UI งานของข้อมูลการจัดเรียงและการเรียกเมธอดไปยังเธรด UI ได้รับการจัดการโดยอัตโนมัติผ่านโมเดลที่อ้างอิงเหตุการณ์ หลีกเลี่ยง BackgroundWorker ถ้า ...
    • แอสเซมบลีของคุณไม่มีหรือไม่โต้ตอบโดยตรงกับ UI
    • คุณต้องการให้เธรดเป็นเธรดเบื้องหน้าหรือ
    • คุณต้องจัดการกับลำดับความสำคัญของเธรด
  2. ใช้ThreadPoolเธรดเมื่อต้องการประสิทธิภาพ ThreadPool ช่วยหลีกเลี่ยงค่าใช้จ่ายที่เกี่ยวข้องกับการสร้างเริ่มต้นและการหยุดเธรด หลีกเลี่ยงการใช้ ThreadPool ถ้า ...
    • งานรันตลอดอายุการใช้งานของแอปพลิเคชันของคุณ
    • คุณต้องการให้เธรดเป็นเธรดเบื้องหน้า
    • คุณต้องจัดการกับลำดับความสำคัญของเธรดหรือ
    • คุณต้องการเธรดที่จะมีข้อมูลประจำตัวที่แน่นอน (ยกเลิก, ระงับ, ค้นหา)
  3. ใช้คลาสThreadสำหรับงานที่ต้องใช้เวลานานและเมื่อคุณต้องการฟีเจอร์ที่เสนอโดยแบบจำลองการทำเธรดอย่างเป็นทางการเช่นการเลือกระหว่างเธรดด้านหน้าและเธรดเบื้องหลังการปรับระดับความสำคัญของเธรดการควบคุมเธรดแบบละเอียด ฯลฯ

10
ผู้ปฏิบัติงานพื้นหลังอยู่ในแอสเซมบลี System.dll และ System.ComponentModelnamespace ไม่มีการพึ่งพา Winforms
Kugel

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

5
เกี่ยวกับประเด็นของคุณเกี่ยวกับการSystem.Windows.Formsชุมนุม BackgroundWorkerยังมีประโยชน์สำหรับแอพ WPF ด้วยและแอพเหล่านั้นอาจไม่มีการอ้างอิงถึง WinForms
GiddyUpHorsey

12

สิ่งที่แมตต์เดวิสพูดนั้นมีคะแนนเพิ่มเติมดังต่อไปนี้:

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

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


1
เท่านั้น ... หวังว่าแอปพลิเคชันจะได้รับการออกแบบเกี่ยวกับวิธีการที่สะอาดในการหยุดภารกิจเธรด (โดยทั่วไปจะไม่ยกเลิก)

11

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


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

เกี่ยวกับ: "... ซึ่งอาจมีความกังวลเนื่องจากมีจำนวน จำกัด เท่านั้น" คุณหมายถึงแอพอื่น ๆ บนระบบปฏิบัติการที่อาจต้องการและแชร์จาก 'พูล' เดียวกันหรือไม่
Dan W

8

คุณรู้ไหมว่าบางครั้งการทำงานกับ BackgroundWorker ง่ายกว่าไม่ว่าคุณจะใช้ Windows Forms, WPF หรือเทคโนโลยีอะไรก็ตาม ส่วนที่เรียบร้อยเกี่ยวกับคนเหล่านี้คือคุณต้องทำเกลียวโดยไม่ต้องกังวลมากเกินไปเกี่ยวกับตำแหน่งที่คุณใช้งานเธรดซึ่งเหมาะสำหรับงานง่าย ๆ

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

BackgroundWorker.CancelAsync()จะตั้งค่าCancellationPendingเป็นtrueแต่จะไม่ทำอะไรมากไปกว่านั้นมันเป็นความรับผิดชอบของเธรดที่จะตรวจสอบสิ่งนี้อย่างต่อเนื่องโปรดทราบว่าคุณสามารถลงเอยด้วยเงื่อนไขการแข่งขันในวิธีการนี้เมื่อผู้ใช้ของคุณยกเลิกCancellationPending.

Thread.Abort() ในทางกลับกันจะมีข้อยกเว้นภายในการดำเนินการเธรดซึ่งบังคับใช้การยกเลิกเธรดนั้นคุณต้องระมัดระวังเกี่ยวกับสิ่งที่อาจเป็นอันตรายหากข้อยกเว้นนี้ถูกยกขึ้นภายในการดำเนินการทันที

เธรดจำเป็นต้องพิจารณาอย่างรอบคอบไม่ว่าจะเป็นงานอะไรสำหรับการอ่านเพิ่มเติม:

การเขียนโปรแกรมแบบขนานใน. NET Framework ที่ มีการจัดการเธรดวิธีปฏิบัติที่ดีที่สุด


5

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

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

อย่าใช้Thread.Abort();แทนให้ส่งสัญญาณเหตุการณ์และออกแบบเธรดของคุณให้จบอย่างสง่างามเมื่อส่งสัญญาณ Thread.Abort()เพิ่มThreadAbortExceptionจุดโดยพลการในการดำเนินการของเธรดซึ่งสามารถทำสิ่งต่าง ๆ ที่ไม่มีความสุขเช่นจอภาพเด็กกำพร้ารัฐที่ใช้ร่วมกันที่เสียหายและอื่น ๆ
http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx


2

ถ้ามันยังไม่พัง - แก้ไขจนกว่ามันจะเป็น ... แค่ล้อเล่น :)

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


2

แตกต่างขั้นพื้นฐานเป็นเช่นคุณกล่าวว่าการสร้างเหตุการณ์ GUI BackgroundWorkerจาก หากเธรดไม่จำเป็นต้องอัพเดตการแสดงผลหรือสร้างเหตุการณ์สำหรับเธรด GUI หลักแสดงว่าเธรดนั้นอาจเป็นเธรดธรรมดา


2

ฉันต้องการชี้ให้เห็นพฤติกรรมหนึ่งอย่างของคลาส BackgroundWorker ที่ยังไม่ได้กล่าวถึง คุณสามารถสร้างเธรดปกติให้ทำงานในพื้นหลังได้โดยตั้งค่าคุณสมบัติ Thread.IsBackground

เธรดพื้นหลังจะเหมือนกับเธรดเบื้องหน้ายกเว้นว่าเธรดพื้นหลังจะไม่ป้องกันกระบวนการจากการยกเลิก [ 1 ]

คุณสามารถทดสอบพฤติกรรมนี้โดยการเรียกวิธีการต่อไปนี้ในตัวสร้างของหน้าต่างแบบฟอร์มของคุณ

void TestBackgroundThread()
{
    var thread = new Thread((ThreadStart)delegate()
    {
        long count = 0;
        while (true)
        {
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        }
    });

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

เมื่อคุณสมบัติ IsBackground ถูกตั้งค่าเป็นจริงและคุณปิดหน้าต่างแอปพลิเคชันของคุณจะยุติการใช้งานปกติ

แต่เมื่อคุณสมบัติ IsBackground ถูกตั้งค่าเป็นเท็จ (โดยค่าเริ่มต้น) และคุณปิดหน้าต่างจากนั้นหน้าต่างจะหายไป แต่กระบวนการจะยังคงทำงานต่อไป

คลาส BackgroundWorker ใช้เธรดที่ทำงานในพื้นหลัง


1

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

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


-1

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

มันช่วยให้คุณควบคุมการลากและวางอย่างเรียบร้อยบนบริการของคุณ แต่ ... ไม่ต้องลองปรับใช้ จะไม่ทำงาน

บริการ: ใช้ System.Timers.Timer System.Windows.Forms.Timer เท่านั้นจะไม่ทำงานแม้ว่าจะพร้อมใช้งานในกล่องเครื่องมือ

บริการ: BackgroundWorkers จะไม่ทำงานเมื่อมันทำงานเป็นบริการใช้ System.Threading.ThreadPools แทนหรือโทร Async

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