Как использовать DoEvents(), не будучи "злым"?

Простой поиск DoEvents приводит множество результатов, которые в основном приводят к:

DoEvents это зло Не используйте это. Вместо этого используйте потоки.

Причины, которые обычно приводятся ниже:

  • Проблемы с повторным входом
  • Низкая производительность
  • Проблемы с юзабилити (например, перетаскивание через отключенное окно)

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

Как они это делают? Как они избегают проблем, процитированных с DoEvents? (Или они?)

3 ответа

Решение

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

  • Вопросы повторного входа. Здесь на самом деле есть две опасности:

    1. Часть проблемы здесь связана со стеком вызовов. Если вы вызываете.DoEvents() в цикле, который сам обрабатывает сообщения, использующие DoEvents() и т. Д., Вы получаете довольно глубокий стек вызовов. Легко использовать DoEvents() и случайно заполнить ваш стек вызовов, что приведет к исключению Stackru. Если вы используете.DoEvents() только в одном или двух местах, вы, вероятно, в порядке. Если это первый инструмент, к которому вы обращаетесь, когда у вас есть длительный процесс, вы можете легко оказаться здесь в беде. Даже одно использование в неправильном месте может позволить пользователю вызвать исключение переполнения стека (иногда просто удерживая клавишу ввода), и это может быть проблемой безопасности.
    2. Иногда можно найти один и тот же метод в стеке вызовов дважды. Если вы не создали метод с учетом этого (подсказка: вы, вероятно, не сделали), тогда могут произойти плохие вещи. Если все, что передано в метод, является типом значения, и нет никакой зависимости от вещей вне метода, то вы можете быть в порядке. Но в противном случае вам нужно тщательно подумать о том, что произойдет, если весь ваш метод снова запустится до того, как вам вернут элемент управления в тот момент, когда вызывается.DoEvents(). Какие параметры или ресурсы вне вашего метода могут быть изменены, чего вы не ожидали? Меняет ли ваш метод какие-либо объекты, где оба экземпляра в стеке могут действовать на один и тот же объект?
  • Проблемы с производительностью. DoEvents() может создать иллюзию многопоточности, но это не настоящая многопоточность. Это имеет как минимум три настоящие опасности:

    1. Когда вы вызываете DoEvents(), вы передаете управление существующим потоком обратно в насос сообщений. Насос сообщений может, в свою очередь, передать управление чему-то другому, и это может занять некоторое время. В результате ваша оригинальная операция может занять гораздо больше времени, чем если бы она находилась в потоке сама по себе, который никогда не даст управление, определенно дольше, чем нужно.
    2. Дублирование работы. Поскольку можно обнаружить, что он запускает один и тот же метод дважды, и мы уже знаем, что этот метод дорогой / длительный (или вам вообще не понадобится DoEvents()), даже если вы учли все упомянутые внешние зависимости выше, поэтому нет никаких побочных эффектов, вы все равно можете повторить много работы.
    3. Другая проблема - крайняя версия первой: возможность тупика. Если что-то еще в вашей программе зависит от завершения вашего процесса и будет блокироваться до тех пор, пока это не произойдет, и эта вещь вызывается насосом сообщений из DoEvents(), ваше приложение застревает и перестает отвечать на запросы. Это может показаться надуманным, но на практике это удивительно легко сделать случайно, а сбои очень трудно найти и отладить позже. Это является корнем некоторых зависших ситуаций приложения, которые вы, возможно, испытывали на своем компьютере.
  • Вопросы юзабилити. Это побочные эффекты, которые возникают из-за неправильного учета других опасностей. Здесь нет ничего нового, если вы правильно смотрели в других местах.

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

Реальность такова, что большую часть времени, по крайней мере в мире.Net, компонент BackgroundWorker почти так же прост, по крайней мере, если вы сделали это один или два раза, и он будет выполнять работу безопасным способом. Совсем недавно, шаблон асинхронного / ожидающего или использование Task может быть гораздо более эффективным и безопасным.

Еще в 16-битные времена Windows, когда каждая задача разделяла один поток, единственным способом обеспечить отзывчивость программы в узком цикле было DoEvents, Именно это немодальное использование не рекомендуется в пользу потоков. Вот типичный пример:

' Process image
For y = 1 To height
    For x = 1 to width
        ProcessPixel x, y
    End For
    DoEvents ' <-- DON'T DO THIS -- just put the whole loop in another thread
End For

Для модальных вещей (таких как отслеживание всплывающих окон), скорее всего, все будет в порядке.

Я могу ошибаться, но мне кажется, что DoDragDrop а также TrackPopupMenu это довольно особые случаи, в которых они принимают пользовательский интерфейс, поэтому не имеют проблемы с повторным входом (я думаю, это основная причина, по которой люди описывают DoEvents как "Зло").

Лично я не думаю, что было бы полезно отклонить функцию как "Зло" - скорее объясните ловушки, чтобы люди могли сами решать. В случае DoEvents Есть редкие случаи, когда его все еще целесообразно использовать, например, когда отображается модальное диалоговое окно прогресса, где пользователь не может взаимодействовать с остальным пользовательским интерфейсом, поэтому нет проблем с повторным входом.

Конечно, если под "Злом" вы подразумеваете "что-то, что не следует использовать без полного понимания ловушек", то я согласен с тем, что DoEvents это зло

Другие вопросы по тегам