การใช้แอปพลิเคชัน DoEvents ()


272

สามารถApplication.DoEvents()ใช้ใน C #?

ฟังก์ชั่นนี้เป็นวิธีที่ทำให้ GUI สามารถติดต่อกับส่วนที่เหลือของแอพได้ในลักษณะเดียวกับที่ VB6 DoEventsทำหรือไม่?


35
DoEventsเป็นส่วนหนึ่งของ Windows Forms ไม่ใช่ภาษา C # ด้วยเหตุนี้จึงสามารถใช้งานได้จากภาษา. NET อย่างไรก็ตามไม่ควรใช้จากภาษา. NET ใด ๆ
— Stephen Cleary

3
ถึงปี 2560 ใช้ Async / Await ดูจุดสิ้นสุดของ @hansPassant คำตอบสำหรับรายละเอียด
— FastAl

คำตอบ:


468

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

ทันทีที่ค้างคาว: โปรแกรม Windows Forms เกือบทุกอันมีการเรียกใช้ DoEvents () มันปลอมตัวอย่างชาญฉลาดอย่างไรก็ตามมีชื่ออื่น: ShowDialog () เป็น DoEvents () ที่อนุญาตให้ไดอะล็อกเป็นโมดัลโดยไม่ต้องแช่แข็งส่วนที่เหลือของหน้าต่างในแอปพลิเคชัน

โปรแกรมเมอร์ส่วนใหญ่ต้องการใช้ DoEvents เพื่อหยุดส่วนติดต่อผู้ใช้ของพวกเขาจากการแช่แข็งเมื่อพวกเขาเขียน modal loop ของตนเอง มันทำอย่างนั้นอย่างแน่นอน; มันส่งข้อความ Windows และรับการร้องขอการส่งมอบสีใด ๆ อย่างไรก็ตามปัญหาคือมันไม่ได้เลือก ไม่เพียงส่งข้อความสีเท่านั้น แต่ยังส่งมอบทุกอย่างอีกด้วย

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

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

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

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

รุ่นต่อไปของ C # และ VB.NET จะให้ปืนที่แตกต่างกับคำหลักใหม่ที่รอคอยและ async แรงบันดาลใจในส่วนเล็ก ๆ จากปัญหาที่เกิดจาก DoEvents และเธรด แต่ส่วนใหญ่มาจากการออกแบบ API ของ WinRT ที่ต้องการให้คุณปรับปรุง UI ของคุณในขณะที่การดำเนินการแบบอะซิงโครนัสเกิดขึ้น ชอบอ่านจากไฟล์


1
นี่เป็นเพียงส่วนปลายของภูเขาน้ำแข็งเท่านั้น ฉันได้ใช้Application.DoEventsโดยไม่มีปัญหาจนกว่าฉันจะเพิ่มคุณลักษณะบางอย่างที่จะ UDP รหัสของฉันซึ่งส่งผลให้ปัญหาที่อธิบายไว้ที่นี่ ฉันชอบที่จะรู้ว่ามีวิธีรอบที่DoEventsมี
— darda

นี่เป็นคำตอบที่ดีสิ่งเดียวที่ฉันรู้สึกว่ามันขาดหายไปคือคำอธิบาย / การอภิปรายเกี่ยวกับการยอมให้ดำเนินการและการออกแบบ thread-safe หรือ timeslicing โดยทั่วไป - แต่นั่นเป็นข้อความที่ง่ายกว่าเดิมถึงสามเท่า :)
— jheriko

5
จะได้ประโยชน์จากโซลูชันที่ใช้งานได้จริงมากกว่า "ใช้เธรด" ตัวอย่างเช่นBackgroundWorkerองค์ประกอบจัดการเธรดสำหรับคุณหลีกเลี่ยงผลลัพธ์ที่มีสีสันส่วนใหญ่ของการถ่ายภาพเท้าและไม่จำเป็นต้องใช้รุ่นภาษา C #
— Ben Voigt

29

อาจเป็นได้ แต่เป็นแฮ็ค

ดูDoEvents Evil หรือไม่ .

ส่งตรงจากหน้า MSDNที่พวกเขาอ้างถึง:

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

ดังนั้นข้อควรระวังของ Microsoft ต่อการใช้งาน

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

ที่นี่ไม่มีลูกผู้ชาย - ถ้ามันทำงานได้อย่างมีประสิทธิภาพฉันก็จะอยู่กับมัน อย่างไรก็ตามการพยายามใช้ DoEvents ใน. NET ทำให้ฉันไม่มีอะไรนอกจากความเจ็บปวด


1
เป็นที่น่าสังเกตว่าโพสต์นั้นมาจากปี 2004 ก่อนหน้า. NET 2.0 และBackgroundWorkerช่วยลดความซับซ้อนของวิธีการ "แก้ไข"
— Justin

ตกลง นอกจากนี้ไลบรารีงานใน. NET 4.0 ก็ค่อนข้างดีเช่นกัน
— RQDQ

1
เหตุใดจึงเป็นแฮ็คถ้า Microsoft ได้จัดให้มีฟังก์ชันเพื่อการใช้งานที่จำเป็นบ่อยครั้ง?
— Craig Johnston

1
@Craig Johnston - อัปเดตคำตอบของฉันให้ละเอียดมากขึ้นว่าทำไมฉันถึงเชื่อว่า DoEvents ตกอยู่ในหมวดหมู่ของแฮ็ค
— RQDQ

บทความสยองขวัญที่เข้ารหัสนั้นเรียกว่า "DoEvents spackle" ยอดเยี่ยม!
— gonzobrains

24

ใช่มีวิธี DoEvents แบบคงที่ในคลาส Application ใน System.Windows.Forms namespace System.Windows.Forms.Application.DoEvents () สามารถใช้เพื่อประมวลผลข้อความที่รออยู่ในคิวบนเธรด UI เมื่อดำเนินงานที่ดำเนินการมานานในเธรด UI สิ่งนี้มีประโยชน์ในการทำให้ UI ดูเหมือนมีการตอบสนองมากขึ้นและไม่ "ถูกล็อก" ในขณะที่ทำงานที่ยาวนาน อย่างไรก็ตามนี่เป็นวิธีที่ดีที่สุดในการทำสิ่งต่างๆ ตามที่ Microsoft เรียก DoEvents "... ทำให้เธรดปัจจุบันถูกหยุดชั่วคราวในขณะที่ประมวลผลข้อความหน้าต่างรอทั้งหมด" หากมีการทริกเกอร์เหตุการณ์อาจมีข้อผิดพลาดที่ไม่คาดคิดและเป็นระยะที่ยากต่อการติดตาม หากคุณมีงานมากมายมันจะดีกว่าที่จะทำในเธรดแยกต่างหาก การรันงานที่ยาวนานในเธรดแยกต่างหากช่วยให้สามารถประมวลผลได้โดยไม่รบกวน UI ที่ทำงานอย่างต่อเนื่อง ดูที่นี่สำหรับรายละเอียดเพิ่มเติม

นี่คือตัวอย่างของวิธีใช้ DoEvents โปรดทราบว่า Microsoft ยังให้ความระมัดระวังในการใช้งาน


13

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

บรรทัดล่างคือ: ถ้าคุณจะใช้ DoEvents คุณจะต้องทดสอบอย่างละเอียดก่อนที่จะปรับใช้แอปพลิเคชันของคุณ


คำตอบที่ดี แต่ถ้าฉันอาจแนะนำวิธีแก้ปัญหาสำหรับแถบความคืบหน้าซึ่งจะดีกว่าฉันจะบอกว่าทำงานของคุณในเธรดที่แยกต่างหากทำให้ตัวบ่งชี้ความคืบหน้าของคุณพร้อมใช้งานในตัวแปรระเหยได้ interlocked และรีเฟรช . วิธีนี้ไม่มีโค้ดการบำรุงรักษาถูกล่อลวงให้เพิ่มรหัสเฮฟวี่เวทในลูปของคุณ
— mg30rg

1
DoEvents หรือเทียบเท่านั้นหลีกเลี่ยงไม่ได้หากคุณมีกระบวนการ UI ที่หนักหน่วงและไม่ต้องการล็อค UI ตัวเลือกแรกคือการไม่ทำการประมวลผล UI ขนาดใหญ่ แต่สิ่งนี้สามารถทำให้โค้ดสามารถบำรุงรักษาได้น้อยลง อย่างไรก็ตามรอ Dispatcher.Yield () ทำสิ่งที่คล้ายกันมากกับ DoEvents และสามารถอนุญาตให้มีกระบวนการ UI ที่ล็อคหน้าจอเพื่อทำขึ้นสำหรับ intents และ async ทั้งหมด
— นักพัฒนาเมลเบิร์น

11

ใช่.

อย่างไรก็ตามหากคุณต้องการใช้Application.DoEventsนี่เป็นข้อบ่งชี้ของการออกแบบแอปพลิเคชั่นที่ไม่ดี บางทีคุณอาจต้องการทำงานในเธรดแยกต่างหากแทน?


3
จะเป็นอย่างไรถ้าคุณต้องการมันเพื่อให้คุณสามารถหมุนและรอให้งานเสร็จสมบูรณ์ในเธรดอื่น
— jheriko

@ jheriko จากนั้นคุณควรลอง async รอ
— mg30rg

5

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

รายละเอียดเพิ่มเติมเกี่ยวกับกรณีของฉัน ...

ฉันทำดังต่อไปนี้ (ตามที่แนะนำที่นี่ ) เพื่อให้แน่ใจว่าหน้าจอสแปลชบาร์ชนิดความคืบหน้า ( วิธีการแสดงภาพซ้อนทับ "โหลด" ... ) อัปเดตระหว่างคำสั่ง SQL ที่รันเป็นเวลานาน:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

ไม่ดี:สำหรับฉันที่เรียก DoEvents นั่นหมายความว่าบางครั้งการคลิกเมาส์ในรูปแบบที่อยู่ด้านหลังหน้าจอเริ่มต้นของฉันแม้ว่าฉันจะทำ TopMost

ดี / คำตอบ:เปลี่ยนสาย DoEvents ด้วยง่ายโทร Refresh เพื่อแผงเล็ก ๆ FormSplash.Panel1.Refresh()ในใจกลางของหน้าจอของฉัน UI ได้รับการอัปเดตเป็นอย่างดีและ DoEvents แปลกประหลาดที่ผู้อื่นเตือนได้หายไป


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

4

ฉันเคยเห็นแอปพลิเคชั่นเชิงพาณิชย์มากมายโดยใช้ "DoEvents-Hack" โดยเฉพาะอย่างยิ่งเมื่อการแสดงผลเข้ามาในการเล่นฉันมักจะเห็นสิ่งนี้:

while(running)
{
    Render();
    Application.DoEvents();
}

พวกเขาทุกคนรู้เกี่ยวกับความชั่วร้ายของวิธีการนั้น อย่างไรก็ตามพวกเขาใช้แฮ็คเพราะพวกเขาไม่รู้จักวิธีแก้ปัญหาอื่น ๆ ต่อไปนี้เป็นวิธีการบางส่วนที่นำมาจากการโพสต์บล็อกโดยTom Miller :

  • ตั้งค่าแบบฟอร์มของคุณเพื่อให้การวาดภาพทั้งหมดเกิดขึ้นใน WmPaint และทำการเรนเดอร์ของคุณที่นั่น ก่อนที่จะสิ้นสุดเมธอด OnPaint ตรวจสอบให้แน่ใจว่าคุณทำสิ่งนี้แล้วประเมินค่า (); นี่จะทำให้วิธี OnPaint ถูกใช้งานอีกครั้งทันที
  • P / เรียกใช้ Win32 API และเรียก PeekMessage / TranslateMessage / DispatchMessage (Doevents ทำสิ่งที่คล้ายกัน แต่คุณสามารถทำได้โดยไม่ต้องจัดสรรเพิ่มเติม)
  • เขียนคลาสฟอร์มของคุณเองซึ่งเป็น wrapper ขนาดเล็กรอบ ๆ CreateWindowEx และให้การควบคุมที่สมบูรณ์แก่คุณในวงข้อความ - ตัดสินใจว่าวิธี DoEvents ทำงานได้ดีสำหรับคุณและติดกับมัน


3

DoEvents อนุญาตให้ผู้ใช้คลิกไปรอบ ๆ หรือพิมพ์และทริกเกอร์เหตุการณ์อื่น ๆ และเธรดพื้นหลังเป็นวิธีที่ดีกว่า

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

รหัสต่อไปนี้บล็อกการป้อนข้อมูลผู้ใช้ทั้งหมดในขณะดำเนินการ DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}

1
คุณควรปิดกั้นกิจกรรมทุกครั้งที่เรียกคนดู มิฉะนั้นเหตุการณ์อื่น ๆ ในแอปของคุณจะเริ่มตอบสนองและแอปของคุณอาจเริ่มต้นทำสองสิ่งพร้อมกัน
— FastAl

2

Application.DoEvents สามารถสร้างปัญหาได้หากมีสิ่งอื่นนอกเหนือจากการประมวลผลกราฟิกในคิวข้อความ

มันจะมีประโยชน์สำหรับการปรับปรุงแถบความคืบหน้าและแจ้งให้ผู้ใช้ของความคืบหน้าในบางสิ่งบางอย่างเช่นการก่อสร้างและโหลด MainForm ถ้ามันใช้เวลาสักครู่

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

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