Может Application.DoEvents()
быть использован в C #?
Является ли эта функция способом, позволяющим GUI догнать остальную часть приложения, во многом так же, как это DoEvents
делают VB6 ?
Может Application.DoEvents()
быть использован в C #?
Является ли эта функция способом, позволяющим GUI догнать остальную часть приложения, во многом так же, как это DoEvents
делают VB6 ?
Ответы:
Хмя, вечная мистика 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, который требует обновления вашего интерфейса во время асинхронной операции. Как чтение из файла.
BackgroundWorker
компонент управляет потоками для вас, избегая большинства ярких результатов выстрелов в ногу, и он не требует передовых версий языка C #.
Это может быть, но это взломать.
Видишь ли DoEvents зло? ,
Прямо со страницы MSDN , на которую ссылается thedev :
Вызов этого метода приводит к приостановке текущего потока, пока обрабатываются все сообщения окна ожидания. Если сообщение приводит к запуску события, могут выполняться другие области кода вашего приложения. Это может привести к тому, что ваше приложение будет демонстрировать неожиданное поведение, которое трудно отладить. Если вы выполняете операции или вычисления, которые занимают много времени, часто предпочтительнее выполнять эти операции в новом потоке. Для получения дополнительной информации об асинхронном программировании см. Обзор асинхронного программирования.
Поэтому Microsoft предостерегает от его использования.
Кроме того, я считаю это хаком, потому что его поведение непредсказуемо и склонно к побочным эффектам (это происходит из-за опыта использования DoEvents вместо раскрутки нового потока или использования фонового рабочего).
Здесь нет мужества - если бы оно работало как надежное решение, я был бы повсюду. Однако попытка использовать DoEvents в .NET не принесла мне ничего, кроме боли.
BackgroundWorker
помог упростить «правильный» путь.
Да, в классе Application в пространстве имен System.Windows.Forms есть статический метод DoEvents. System.Windows.Forms.Application.DoEvents () можно использовать для обработки сообщений, ожидающих в очереди в потоке пользовательского интерфейса, при выполнении длительной задачи в потоке пользовательского интерфейса. Это имеет то преимущество, что пользовательский интерфейс выглядит более отзывчивым и не «заблокированным» во время выполнения длинной задачи. Тем не менее, это почти всегда НЕ лучший способ сделать что-то. Согласно заявлению Microsoft, вызов DoEvents «... приводит к приостановке текущего потока во время обработки всех сообщений окна ожидания». Если событие инициировано, есть вероятность неожиданных и периодических ошибок, которые трудно отследить. Если у вас обширная задача, гораздо лучше сделать это в отдельной ветке. Запуск длинных задач в отдельном потоке позволяет обрабатывать их, не мешая пользовательскому интерфейсу продолжать работать гладко. Смотретьздесь для более подробной информации.
Вот пример того, как использовать DoEvents; Обратите внимание, что Microsoft также предостерегает от его использования.
Исходя из моего опыта, я бы посоветовал с большой осторожностью использовать DoEvents в .NET. Я испытал некоторые очень странные результаты при использовании DoEvents в TabControl, содержащем DataGridViews. С другой стороны, если все, с чем вы имеете дело, это небольшая форма с индикатором выполнения, то все может быть в порядке.
Суть в том, что если вы собираетесь использовать DoEvents, вам необходимо тщательно протестировать его перед развертыванием приложения.
Да.
Однако, если вам нужно использовать Application.DoEvents
, это в основном указывает на плохой дизайн приложения. Возможно, вы хотели бы выполнить какую-то работу в отдельном потоке?
Я видел комментарий 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, о которой другие предупреждали, исчезла.
Я видел много коммерческих приложений, использующих «DoEvents-Hack». Особенно, когда рендеринг вступает в игру, я часто вижу это:
while(running)
{
Render();
Application.DoEvents();
}
Все они знают о зле этого метода. Тем не менее, они используют взломать, потому что они не знают другого решения. Вот некоторые подходы , взятые из блога по Том Миллер :
- Установите форму, чтобы все рисование происходило в WmPaint, и выполняйте рендеринг там. Перед завершением метода OnPaint убедитесь, что вы делаете this.Invalidate (); Это приведет к немедленному повторному запуску метода OnPaint.
- P / Invoke в Win32 API и вызов PeekMessage / TranslateMessage / DispatchMessage. (Doevents на самом деле делает нечто подобное, но вы можете сделать это без дополнительных выделений).
- Напишите свой собственный класс форм, который является небольшой оболочкой для CreateWindowEx, и дайте себе полный контроль над циклом сообщений. - Решите, что метод DoEvents хорошо работает для вас, и придерживайтесь его.
Проверьте документацию MSDN для Application.DoEvents
метода.
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
}
}
Application.DoEvents может создавать проблемы, если в очередь сообщений помещается что-то кроме обработки графики.
Это может быть полезно для обновления индикаторов выполнения и уведомления пользователя о прогрессе в чем-то вроде создания и загрузки MainForm, если это занимает некоторое время.
В недавнем приложении, которое я сделал, я использовал DoEvents для обновления некоторых меток на экране загрузки каждый раз, когда в конструкторе моей MainForm выполняется блок кода. В этом случае поток пользовательского интерфейса был занят отправкой электронной почты на SMTP-сервер, который не поддерживал вызовы SendAsync (). Возможно, я мог бы создать другой поток с методами Begin () и End () и вызвать из них метод Send (), но этот метод подвержен ошибкам, и я бы предпочел, чтобы основная форма моего приложения не генерировала исключения во время конструирования.
DoEvents
является частью Windows Forms, а не языка C #. Как таковой, он может быть использован с любого языка .NET. Тем не менее, он не должен использоваться с любым языком .NET.