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