การใช้แอปพลิเคชัน 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.