Использование Application.DoEvents () - PullRequest
258 голосов
/ 03 марта 2011

Может ли Application.DoEvents() использоваться в C #?

Является ли эта функция способом, позволяющим графическому интерфейсу догнать остальную часть приложения, во многом так же, как это делает DoEvents в VB6?

Ответы [ 10 ]

452 голосов
/ 03 марта 2011

Хмя, вечная мистика 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, который требует , чтобы вы обновляли свой пользовательский интерфейс во время асинхронной операции.Как чтение из файла.

29 голосов
/ 03 марта 2011

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

См. Зло DoEvents? .

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

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

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

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

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

23 голосов
/ 03 марта 2011

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

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

13 голосов
/ 22 марта 2011

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

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

11 голосов
/ 03 марта 2011

Да.

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

4 голосов
/ 04 декабря 2013

Я видел комментарий jheriko выше и изначально соглашался с тем, что не могу найти способ избежать использования DoEvents, если вы закончите вращать свой основной поток пользовательского интерфейса, ожидая завершения асинхронного фрагмента кода в другом потоке. Но из ответа Матиаса простое обновление маленькой панели в моем пользовательском интерфейсе может заменить DoEvents (и избежать неприятного побочного эффекта).

Подробнее о моем случае ...

Я делал следующее (как предложено здесь ), чтобы гарантировать, что заставка типа индикатора выполнения ( Как отобразить оверлей "loading" ... ) обновлена ​​во время длительная команда 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 простым вызовом "Обновить" на маленькую панель в центре моего заставки, FormSplash.Panel1.Refresh(). Пользовательский интерфейс хорошо обновляется, и странность DoEvents, о которой другие предупреждали, исчезла.

4 голосов
/ 20 декабря 2011

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

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

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

  • Установите свою форму так, чтобы все рисование происходило в WmPaint, и выполнитеваш рендеринг там.Перед завершением метода OnPaint убедитесь, что вы делаете this.Invalidate ();Это приведет к немедленному повторному запуску метода OnPaint.
  • P / Invoke в Win32 API и вызов PeekMessage / TranslateMessage / DispatchMessage.(На самом деле Doevents делает нечто подобное, но вы можете сделать это без дополнительных выделений).
  • Напишите свой собственный класс форм, который является небольшой оболочкой для CreateWindowEx, и дайте себе полный контроль над циклом сообщений.- Решите, что метод DoEvents вам подходит, и придерживайтесь его.
3 голосов
/ 03 марта 2011

Ознакомьтесь с документацией MSDN для метода Application.DoEvents.

2 голосов
/ 20 сентября 2014

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

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

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

1 голос
/ 03 января 2019

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
    }
}
...