Использование 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 появится новое оружие с новыми ключевыми словами await и async. В какой-то мере это вызвано проблемами, вызванными DoEvents и потоками, но в значительной степени - дизайном API WinRT, который требует обновления вашего интерфейса во время асинхронной операции. Как чтение из файла.

Ганс Пассант
источник
1
Это только верхушка айсберга. Я использовал Application.DoEventsбез проблем, пока я не добавил некоторые функции UDP в мой код, что привело к проблеме, описанной здесь . Я хотел бы знать, есть ли способ обойти это с DoEvents.
Дарда
это хороший ответ, единственное, что мне не хватает, это объяснение / обсуждение того, как привести к выполнению, поточно-ориентированному дизайну или временному сращиванию в целом - но это легко в три раза больше текста снова. :)
jheriko
5
Было бы полезно получить более практичное решение, чем обычно использовать потоки. Например, BackgroundWorkerкомпонент управляет потоками для вас, избегая большинства ярких результатов выстрелов в ногу, и он не требует передовых версий языка C #.
Бен Фойгт
29

Это может быть, но это взломать.

Видишь ли DoEvents зло? ,

Прямо со страницы MSDN , на которую ссылается thedev :

Вызов этого метода приводит к приостановке текущего потока, пока обрабатываются все сообщения окна ожидания. Если сообщение приводит к запуску события, могут выполняться другие области кода вашего приложения. Это может привести к тому, что ваше приложение будет демонстрировать неожиданное поведение, которое трудно отладить. Если вы выполняете операции или вычисления, которые занимают много времени, часто предпочтительнее выполнять эти операции в новом потоке. Для получения дополнительной информации об асинхронном программировании см. Обзор асинхронного программирования.

Поэтому Microsoft предостерегает от его использования.

Кроме того, я считаю это хаком, потому что его поведение непредсказуемо и склонно к побочным эффектам (это происходит из-за опыта использования DoEvents вместо раскрутки нового потока или использования фонового рабочего).

Здесь нет мужества - если бы оно работало как надежное решение, я был бы повсюду. Однако попытка использовать DoEvents в .NET не принесла мне ничего, кроме боли.

RQDQ
источник
1
Стоит отметить, что этот пост относится к 2004 году, до .NET 2.0 и BackgroundWorkerпомог упростить «правильный» путь.
Джастин
Согласовано. Кроме того, библиотека задач в .NET 4.0 также довольно хороша.
RQDQ
1
С какой стати взломать, если Microsoft предоставила функцию для той цели, для которой она часто нужна?
Крейг Джонстон
1
@Craig Johnston - обновил мой ответ, чтобы более подробно объяснить, почему я считаю, что DoEvents попадает в категорию хакеров.
RQDQ
Эта статья ужасов кодирования назвала это «DoEvents spackle». Brilliant!
gonzobrains
24

Да, в классе Application в пространстве имен System.Windows.Forms есть статический метод DoEvents. System.Windows.Forms.Application.DoEvents () можно использовать для обработки сообщений, ожидающих в очереди в потоке пользовательского интерфейса, при выполнении длительной задачи в потоке пользовательского интерфейса. Это имеет то преимущество, что пользовательский интерфейс выглядит более отзывчивым и не «заблокированным» во время выполнения длинной задачи. Тем не менее, это почти всегда НЕ лучший способ сделать что-то. Согласно заявлению Microsoft, вызов DoEvents «... приводит к приостановке текущего потока во время обработки всех сообщений окна ожидания». Если событие инициировано, есть вероятность неожиданных и периодических ошибок, которые трудно отследить. Если у вас обширная задача, гораздо лучше сделать это в отдельной ветке. Запуск длинных задач в отдельном потоке позволяет обрабатывать их, не мешая пользовательскому интерфейсу продолжать работать гладко. Смотретьздесь для более подробной информации.

Вот пример того, как использовать DoEvents; Обратите внимание, что Microsoft также предостерегает от его использования.

Билл W
источник
13

Исходя из моего опыта, я бы посоветовал с большой осторожностью использовать DoEvents в .NET. Я испытал некоторые очень странные результаты при использовании DoEvents в TabControl, содержащем DataGridViews. С другой стороны, если все, с чем вы имеете дело, это небольшая форма с индикатором выполнения, то все может быть в порядке.

Суть в том, что если вы собираетесь использовать DoEvents, вам необходимо тщательно протестировать его перед развертыванием приложения.

Крейг Джонстон
источник
Хороший ответ, но если я могу предложить решение для индикатора выполнения, которое лучше , я бы сказал, сделайте свою работу в отдельном потоке, сделайте свой индикатор прогресса доступным в изменчивой, взаимосвязанной переменной и обновите свой индикатор выполнения из таймера , Таким образом, ни один обслуживающий кодер не испытывает искушения добавить в свой цикл тяжелый код.
mg30rg
1
DoEvents или его эквивалент неизбежны, если у вас здоровенные процессы пользовательского интерфейса и вы не хотите блокировать пользовательский интерфейс. Первый вариант - не делать больших кусков обработки пользовательского интерфейса, но это может сделать код менее обслуживаемым. Однако await Dispatcher.Yield () делает то же самое, что и DoEvents, и может по существу позволить процессу пользовательского интерфейса, который блокирует экран, быть выполненным для всех намерений и целей асинхронно.
Разработчик из Мельбурна
11

Да.

Однако, если вам нужно использовать Application.DoEvents, это в основном указывает на плохой дизайн приложения. Возможно, вы хотели бы выполнить какую-то работу в отдельном потоке?

Фредерик Гейселс
источник
3
Что делать, если вы хотите, чтобы вы могли вращаться и ждать завершения работы в другом потоке?
Джерико
@jheriko Тогда тебе стоит попробовать async-await.
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, о которой другие предупреждали, исчезла.

TamW
источник
3
Обновить не обновляет окно, хотя. Если пользователь выберет другое окно на рабочем столе, нажатие на него не окажет никакого влияния, и ОС отобразит ваше приложение как не отвечающее. DoEvents () делает намного больше, чем обновление, поскольку взаимодействует с ОС через систему обмена сообщениями.
ThunderGr
4

Я видел много коммерческих приложений, использующих «DoEvents-Hack». Особенно, когда рендеринг вступает в игру, я часто вижу это:

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

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

  • Установите форму, чтобы все рисование происходило в WmPaint, и выполняйте рендеринг там. Перед завершением метода OnPaint убедитесь, что вы делаете this.Invalidate (); Это приведет к немедленному повторному запуску метода OnPaint.
  • P / Invoke в Win32 API и вызов 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
    }
}
cat_in_hat
источник
1
Вы должны всегда блокировать события при вызове doevents. В противном случае другие события в вашем приложении будут срабатывать в ответ, и ваше приложение может начать делать две вещи одновременно.
FastAl
2

Application.DoEvents может создавать проблемы, если в очередь сообщений помещается что-то кроме обработки графики.

Это может быть полезно для обновления индикаторов выполнения и уведомления пользователя о прогрессе в чем-то вроде создания и загрузки MainForm, если это занимает некоторое время.

В недавнем приложении, которое я сделал, я использовал DoEvents для обновления некоторых меток на экране загрузки каждый раз, когда в конструкторе моей MainForm выполняется блок кода. В этом случае поток пользовательского интерфейса был занят отправкой электронной почты на SMTP-сервер, который не поддерживал вызовы SendAsync (). Возможно, я мог бы создать другой поток с методами Begin () и End () и вызвать из них метод Send (), но этот метод подвержен ошибкам, и я бы предпочел, чтобы основная форма моего приложения не генерировала исключения во время конструирования.

guest123
источник