it-swarm.com.ru

Использование Application.DoEvents ()

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

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

245
Craig Johnston

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

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

13
Craig Johnston

Хмя, вечная мистика DoEvents (). Было огромное количество негативной реакции, но никто никогда не объяснял, почему это «плохо». Такая же мудрость, как и "не мутируй структуру". Хм, почему среда выполнения и язык поддерживают изменение структуры, если это так плохо? Причина та же: вы стреляете себе в ногу, если не делаете это правильно. Без труда. И чтобы сделать это правильно, нужно знать точно, что он делает, что в случае DoEvents () определенно нелегко.

Сразу же: практически любая программа Windows Forms фактически содержит вызов DoEvents (). Он хитро замаскирован, но с другим именем: ShowDialog (). Именно DoEvents () позволяет диалогу быть модальным, не замораживая остальные окна в приложении.

Большинство программистов хотят использовать DoEvents, чтобы предотвратить зависание их пользовательского интерфейса при написании собственного модального цикла. Это, безусловно, делает это; он отправляет сообщения Windows и получает любые запросы Paint. Проблема, однако, в том, что он не избирателен. Он не только отправляет сообщения Paint, но и доставляет все остальное.

И есть набор уведомлений, которые вызывают проблемы. Они приходят примерно с 3 футов перед монитором. Например, пользователь может закрыть главное окно во время работы цикла, который вызывает DoEvents (). Это работает, пользовательский интерфейс исчез. Но ваш код не остановился, он все еще выполняет цикл. Это плохо. Очень очень плохо.

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

Вернуться к ShowDialog (). Он выполняет DoEvents (), но обратите внимание, что он делает что-то еще. Он отключает все окна в приложении, кроме диалогового окна. Теперь, когда проблема с 3 футами решена, пользователь не может ничего сделать, чтобы испортить логику. Режимы закрытия окна и запуска задания снова решены. Другими словами, пользователь не может заставить вашу программу выполнять код в другом порядке. Он будет выполняться предсказуемо, как и при тестировании кода. Это делает диалоги чрезвычайно раздражающими; кто не ненавидит активный диалог и неспособность копировать и вставлять что-то из другого окна? Но это цена.

Вот что нужно для безопасного использования DoEvents в вашем коде. Установка для свойства Enabled всех ваших форм значения false - это быстрый и эффективный способ избежать проблем. Конечно, ни одному программисту никогда не нравится это делать. И нет. Вот почему вы не должны использовать DoEvents (). Вы должны использовать темы. Даже при том, что они передают вам полный арсенал способов выстрелить в вашу ногу красочными и непостижимыми способами. Но с тем преимуществом, что вы стреляете только собственной ногой; как правило, он не позволяет пользователю стрелять в нее.

В следующих версиях C # и VB.NET появится новое оружие с новыми ключевыми словами await и async. В какой-то мере это вызвано проблемами, вызванными DoEvents и потоками, но в значительной степени дизайном API WinRT, который требует, чтобы вы обновляли пользовательский интерфейс во время выполнения асинхронной операции. Как чтение из файла.

444
Hans Passant

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

Смотрите Зло DoEvents?.

Прямо из на странице MSDN that thedev ссылка:

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

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

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

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

27
RQDQ

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

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

23
Bill W

Да.

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

10
Frederik Gheysels

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

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

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

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

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

3
thedev

Я видел комментарий 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
TamW

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

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

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

2
Guest123

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
    }
}
0
FoundWaldoException