Використання Application.DoEvents ()


272

Можна Application.DoEvents()використовувати в C #?

Чи є ця функція способом дозволити GUI наздоганяти решту програми, приблизно так само, як це DoEventsробить VB6 ?


35
DoEventsє частиною Windows Forms, а не мовою C #. Таким чином, його можна використовувати з будь-якої мови .NET. Однак його не слід використовувати з будь-якої мови .NET.
Стівен Клірі

3
Це 2017. Використовуйте Async / Await. Детальніше див. У кінці відповіді @hansPassant.
FastAl

Відповіді:


468

Хмя, невпинна загадка DoEvents (). Проти цього було дуже багато, але ніхто ніколи не пояснює, чому це "погано". Така ж мудрість, як "не мутуйте структуру". Ем, чому час виконання та мова підтримує мутацію структури, якщо це так погано? Ця ж причина: ви стріляєте собі в ногу, якщо не зробите це правильно. Легко. А правильно робити потрібно точно знати , що це робить, що у випадку з DoEvents () точно не просто.

Безпосередньо: майже будь-яка програма Windows Forms фактично містить виклик DoEvents (). Він хитро замаскований, проте з іншою назвою: ShowDialog (). Саме DoEvents () дозволяє діалогове вікно діалогу без його заморожування решти вікон у додатку.

Більшість програмістів хочуть використовувати DoEvents, щоб зупинити їх користувацький інтерфейс від замерзання, коли вони записують власний модальний цикл. Це, безумовно, робить це; він розсилає повідомлення Windows і отримує будь-які запити на фарбу. Однак проблема полягає в тому, що вона не є вибірковою. Він не тільки розсилає повідомлення з фарбою, але і все інше.

І є набір сповіщень, які спричиняють проблеми. Вони виходять близько 3 футів перед монітором. Наприклад, користувач може закрити головне вікно, поки працює цикл, який викликає DoEvents (). Це працює, користувальницького інтерфейсу вже немає. Але ваш код не зупинився, він все ще виконує цикл. Це погано. Дуже, дуже погано.

Є ще: Користувач може натиснути той самий пункт меню або кнопку, що викликає той самий цикл для початку. Тепер у вас є дві вкладені петлі, що виконують DoEvents (), попередній цикл призупиняється, а новий цикл починається з нуля. Це могло б спрацювати, але хлопці шанси невеликі. Особливо, коли вкладений цикл закінчується, а призупинений поновлюється, намагаючись закінчити роботу, яка вже була завершена. Якщо це не бомбить за винятком, то, безумовно, дані зашифровані всі до пекла.

Повернутися до ShowDialog (). Він виконує DoEvents (), але зауважте, що він робить щось інше. Він вимикає всі вікна програми , крім діалогового. Тепер, коли проблема з 3 футами вирішена, користувач не може зробити нічого, щоб зіпсувати логіку. Як режими відмови закрити вікно, так і знов розпочати роботу. Або, кажучи іншим способом, користувач не може зробити код програми в іншому порядку. Вона буде виконана передбачувано, як і під час тестування коду. Це робить діалоги надзвичайно дратівливими; хто не ненавидить активний діалог і не в змозі скопіювати і вставити щось з іншого вікна? Але це ціна.

Що потрібно для безпечного використання DoEvents у коді. Встановлення властивості Enabled усіх ваших форм на false, це швидкий та ефективний спосіб уникнути проблем. Звичайно, жоден програміст ніколи насправді не любить це робити. І ні. Ось чому не слід використовувати DoEvents (). Ви повинні використовувати нитки. Незважаючи на те, що вони передають вам повний арсенал способів стріляти ногою кольоровими та неприступними способами. Але з тією перевагою, що ти стріляєш лише власною ногою; це (як правило) не дозволяє користувачеві знімати її.

Наступні версії C # і VB.NET нададуть інший пістолет із новими ключовими словами очікування та асинхронізації. Невелика частина надихнулася неприємностями, спричиненими DoEvents і потоками, але значною мірою дизайном API WinRT, який вимагає від вас постійно оновлювати інтерфейс користувача під час асинхронної операції. Як і читання з файлу.


1
Це лише верхівка крижаного Берга. Я використовував Application.DoEventsбез проблем, поки не додав у свій код деякі функції UDP, внаслідок чого тут була описана проблема . Я хотів би знати , якщо є спосіб обійти , що з DoEvents.
darda

це приємна відповідь, єдине, що я відчуваю, що це не вистачає, - це пояснення / дискусія щодо отримання виконання та безпечного дизайну потоків або загального набору часу в цілому - але це знову легко втричі більше тексту. :)
jheriko

5
Виграє корисніше практичне рішення, ніж зазвичай "використання ниток". Наприклад, BackgroundWorkerкомпонент керує потоками для вас, уникаючи більшості барвистих результатів зйомки ніг, і він не вимагає кровоточуючих крайових версій мови C #.
Ben Voigt

29

Може бути, але це хак.

Дивіться, чи DoEvents Evil? .

Прямо зі сторінки MSDN , на яку посилається thedev :

Виклик цього методу призупиняє поточний потік під час обробки всіх повідомлень у вікні очікування. Якщо повідомлення викликає ініціювання події, то можуть виконуватись інші області коду вашої програми. Це може призвести до того, що ваша програма проявить несподівані поведінки, які важко налагодити. Якщо ви виконуєте операції чи обчислення, що займають тривалий час, часто бажано виконувати ці операції на новому потоці. Для отримання додаткової інформації про асинхронне програмування див. Огляд асинхронного програмування.

Тож Microsoft застерігає від його використання.

Крім того, я вважаю це злом, оскільки його поведінка є непередбачуваною і схильною до побічних ефектів (це випливає з досвіду спроб використовувати DoEvents замість того, щоб закручувати новий потік або використовувати фонового працівника).

Тут немає жодного махізму - якби це працювало як надійне рішення, я б перейнявся цим. Однак спроба використовувати DoEvents в .NET не заподіювала мені нічого, крім болю.


1
Варто зауважити, що ця посада була з 2004 року, раніше .NET 2.0 і BackgroundWorkerдопомогла спростити "правильний" спосіб.
Джастін

Домовились. Також досить непогана бібліотека завдань у .NET 4.0.
RQDQ

1
Чому це був би хакер, якщо Microsoft надала функцію для тієї самої мети, для якої вона часто потрібна?
Крейг Джонстон

1
@Craig Johnston - оновив свою відповідь, щоб більш докладно пояснити, чому я вважаю, що DoEvents потрапляє в категорію хакерства.
RQDQ

Ця стаття, що кодує жах, називала її "DoEvents spackle". Блискуче!
gonzobrains

24

Так, у класі Application в просторі імен System.Windows.Forms існує статичний метод DoEvents. System.Windows.Forms.Application.DoEvents () може використовуватися для обробки повідомлень, що чекають у черзі в потоці інтерфейсу користувача, під час виконання тривалого завдання в потоці інтерфейсу користувача. Це має перевагу в тому, що користувальницький інтерфейс здається більш чуйним та не "замкненим" під час тривалого завдання. Однак це майже завжди НЕ найкращий спосіб робити речі. За заявою Microsoft, що викликає DoEvents, "... змушує призупиняти поточну нитку під час обробки всіх повідомлень у вікні очікування". Якщо подія викликана, є потенціал для несподіваних та переривчастих помилок, які важко відстежити. Якщо у вас є велике завдання, набагато краще зробити це окремою темою. Виконання довгих завдань в окремому потоці дозволяє обробляти їх, не заважаючи користувальницькому інтерфейсу продовжувати працювати безперебійно. Подивітьсятут для більш детальної інформації.

Ось приклад того, як використовувати DoEvents; зауважте, що Microsoft також передбачає застереження від його використання.


13

Зі свого досвіду я б порадив велику обережність щодо використання DoEvents в .NET. У мене виникли дуже дивні результати при використанні DoEvents в TabControl, що містить DataGridViews. З іншого боку, якщо все, з чим ви маєте справу, - це невелика форма з смужкою прогресу, то це може бути нормально.

Суть полягає в тому, що якщо ви збираєтесь використовувати DoEvents, то вам потрібно ретельно перевірити його перед тим, як розгорнути свою програму.


Приємна відповідь, але якщо я можу запропонувати рішення для речі прогресу, яка є кращою , я б сказав, виконайте свою роботу в окремому потоці, зробіть показник прогресу доступним у мінливій, замкненій змінній та оновіть панель прогресу від таймера . Таким чином, жоден кодер з технічного обслуговування не спокушається додати важкий код у ваш цикл.
mg30rg

1
DoEvents або подібний еквівалент неминучий, якщо у вас є обтяжені процеси інтерфейсу та не хочете блокувати інтерфейс користувача. Перший варіант - не робити великих фрагментів обробки інтерфейсу, але це може зробити код менш ретельним. Однак, очікуйте, що Dispatcher.Yield () робить дуже подібну річ до DoEvents, і по суті може дозволити процес користувальницького інтерфейсу, який блокує екран, зробити для всіх асинхронних цілей та цілей.
Мельбурн Розробник

11

Так.

Однак якщо вам потрібно використовувати Application.DoEvents, це здебільшого свідчить про погану конструкцію програми. Можливо, ви хотіли б замість цього виконати якусь роботу в окремій нитці?


3
Що робити, якщо ви цього хочете, щоб ви могли обернутися і чекати, коли робота завершиться в іншій нитці?
jheriko

@jheriko Тоді вам справді слід спробувати async-wait.
mg30rg

5

Я побачив коментар jheriko вище і спочатку погодився, що я не можу знайти спосіб уникнути використання DoEvents, якщо ви в кінцевому підсумку будете крутити ваш основний потік інтерфейсу, чекаючи, коли довгий побіжний асинхронний фрагмент коду на іншому потоці завершиться. Але з відповіді Маттіаса просте оновлення невеликої панелі мого інтерфейсу може замінити 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(). Інтерфейс користувача прекрасно оновлюється, і про дивовижність DoEvents, про яку попереджають інші, вже не було.


3
Однак Refresh не оновлює вікно. Якщо користувач вибере інше вікно на робочому столі, натискання назад до вашого вікна не матиме жодного ефекту, і ОС відобразить вашу програму як невідповідну. DoEvents () робить набагато більше, ніж оновлення, оскільки взаємодіє з ОС через систему обміну повідомленнями.
ThunderGr

4

Я бачив багато комерційних додатків, використовуючи "DoEvents-Hack". Особливо, коли візуалізація починає грати, я часто бачу таке:

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

Всі вони знають про зло цього методу. Однак вони використовують хак, бо не знають жодного іншого рішення. Ось деякі підходи , взяті з блогу по Том Міллер :

  • Встановіть форму, щоб усі малюнки відбувалися на WmPaint, і зробіть її візуалізацію там. До закінчення методу OnPaint переконайтесь, що ви це зробите. Invalidate (); Це призведе до негайного запуску методу OnPaint негайно.
  • P / Закликайте до API Win32 та телефонуйте PeekMessage / TranslateMessage / DispatchMessage. (Doevents насправді робить щось подібне, але ви можете це зробити без зайвих асигнувань).
  • Напишіть свій власний клас форм, який є невеликою обгорткою навколо 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. У цьому випадку потік інтерфейсу був зайнятий надсиланням електронної пошти на SMTP-сервер, який не підтримував виклики SendAsync (). Можливо, я міг би створити інший потік методами Begin () та End () і назвав Send () від своїх, але цей метод схильний до помилок, і я вважаю за краще, щоб основна форма моєї програми не викидала виключень під час створення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.