Invoke() блокирует

Время от времени GUI моих приложений перестает перерисовываться. Существует множество потоков, которые запускают всевозможные события (например, таймеры или готовность сетевых данных и т. д.). Также есть много элементов управления, которые подписываются на эти события. Из-за этого все обработчики событий играют в игру InvokeRequired/Invoke. Теперь я понял, что когда графический интерфейс зависает, многие потоки ждут возврата Invoke(). Похоже, насос сообщений перестал качаться. Обработчики выглядят так:

private void MyEventHandler( object sender, EventArgs e ) {
    if ( InvokeRequired ) {
        Invoke( new EventHandler( MyEventHandler ), sender, e );
        return;
    }

    SetSomeStateVariable();
    Invalidate();
}

Любые идеи?

Решение: BeginInvoke(). Похоже, вы всегда должны использовать BeginInvoke(), если у вас много событий CrossThread...

Спасибо.

Спасибо всем.

EDIT: Похоже, BeginInvoke() действительно решил эту проблему. Пока не заморочился.


person EricSchaefer    schedule 13.11.2008    source источник
comment
Кого выстраивают, когда он замерзает? Это один конкретный элемент управления или событие, или оно случайное?   -  person    schedule 13.11.2008
comment
Я не очень понимаю, что вы имеете в виду.   -  person EricSchaefer    schedule 13.11.2008
comment
Ваши события передаются в поток пользовательского интерфейса по одному. Кто-то блокирует во время этого процесса. Я спрашиваю, упорядочивается ли один и тот же метод каждый раз, когда блокируется, и/или обновляется ли один и тот же элемент управления, который блокируется каждый раз.   -  person    schedule 13.11.2008
comment
Я не знаю, потому что я ловил это в отладчике только один раз. Время от времени означает, может быть, десять раз за 6 месяцев и в этом случае только один раз на коробке с отладчиком.   -  person EricSchaefer    schedule 13.11.2008


Ответы (4)


Invoke ожидает, пока событие не будет обработано в потоке GUI. Если вы хотите, чтобы это было асинхронно, используйте BeginInvoke()

person Lou Franco    schedule 13.11.2008
comment
Я знаю это. BeginInvoke, вероятно, просто скроет проблему. Все вызовы блокируются на неопределенный срок. - person EricSchaefer; 13.11.2008
comment
Эрик Шефер, ты упускаешь суть. Invoke ждет, пока событие, которое вы вызвали, не будет выполнено. Однако вызов будет вызван только после завершения выполнения текущей функции. Вы зашли в тупик. BeginInvoke не будет ждать выполнения вызываемой функции, поэтому нет взаимоблокировки. - person Joel Lucsy; 13.11.2008
comment
Может быть, я упускаю больше, чем один пункт. Событие, которое вызывает MyEventHandler, запускается другим потоком (например, тем, который ожидает данные из сети), а не событием пользовательского интерфейса. И зависает только время от времени. - person EricSchaefer; 13.11.2008
comment
Ваша проблема в том, что два потока, которые запущены примерно в одно и то же время, блокируют друг друга - ни один вызов Invoke не может быть завершен. Таким образом, BeginInvoke должен быть безопасным. - person Joel Coehoorn; 13.11.2008
comment
Ох, хорошо. Теперь я понимаю. Я попробую это. Но, пожалуйста, не задерживайте дыхание, потому что проблему нелегко воспроизвести. Спасибо. - person EricSchaefer; 13.11.2008
comment
Я попытался зарегистрировать оскорбительный Invoke (см. Сообщение Wills), и графический интерфейс не зависал до сих пор. Я изменил все Invoke() на BeginInvoke(). Большое спасибо. - person EricSchaefer; 05.12.2008

Может тупик? Вы следите за тем, чтобы события никогда не запускались при удерживании блокировки?

Вы можете увидеть это с подключенным отладчиком? Если это так, заморозьте его, а затем нажмите кнопку «пауза» — и посмотрите, что делает поток пользовательского интерфейса.

Обратите внимание, что если вы можете использовать BeginInvoke вместо Invoke, жизнь становится немного проще, поскольку она не блокируется.

Также обратите внимание, что вам не нужен бит «новый EventHandler» — просто

Invoke((EventHandler) MyEventHandler, sender, e);

все должно быть в порядке.

person Jon Skeet    schedule 13.11.2008
comment
Просто по памяти: я думаю, вам следует вызвать Invoke() для соответствующего элемента управления. Затем элемент управления должен выполнить метод в потоке графического интерфейса. Пожалуйста, скажите мне, если я ошибаюсь. - person lowglider; 13.11.2008
comment
@EricShaefer: Если вы откроете стек для каждого потока, должно быть довольно очевидно, какой из них является потоком пользовательского интерфейса :) - person Jon Skeet; 13.11.2008

Наблюдая за этим вопросом, я вижу, что вы не получите никаких ответов, которые немедленно решат проблему, поскольку большинство из них требуют от вас отладки события, а это происходит так редко, что это почти невозможно. Итак, позвольте мне предложить вам внести некоторые изменения в код, которые могут помочь вам определить виновника в полевых условиях.

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

В теле этого метода я предлагаю вам поставить в очередь эту информацию (метод, описание) и немедленно вернуться.

Очередь должна обслуживаться одним потоком, который выталкивает пару действие/сообщение из очереди, записывает текущее время и описание действия в пару свойств, а затем вызывает действие. Когда действие возвращается, описание и время очищаются (ваш DateTime может принимать значение NULL или устанавливать его в DateTime.Max). Обратите внимание: поскольку все вызовы маршалируются по одному в поток пользовательского интерфейса, вы ничего не теряете, обслуживая здесь очередь одним потоком.

Теперь, вот где мы добираемся до сути этого. Наш класс Invoking должен иметь поток пульса System.Threading.Timer. Это НЕ должен быть объект windows.forms.timer, поскольку он выполняется в потоке пользовательского интерфейса (и будет заблокирован, когда пользовательский интерфейс заблокирован!!!).

Работа этого таймера состоит в том, чтобы периодически просматривать время, когда текущее действие было вызвано. Если DateTime.Now - BeginTime > X, таймер пульса решит, что это действие заблокировано. Таймер сердцебиения будет ЗАПИСЫВАТЬ (как бы вы ни регистрировали) ОПИСАНИЕ, записанное для этого Действия. Теперь у вас есть запись того, что происходило в то время, когда ваш пользовательский интерфейс заблокирован, и вы можете лучше его отлаживать.

Я знаю, что это не ответ на вашу проблему, но, по крайней мере, делая это, вы можете получить хорошее представление о том, что происходит в то время, когда вы заблокированы.

person Community    schedule 13.11.2008
comment
Хорошие идеи. В любом случае мне придется перебрать все 55 (!) вызовов. - person EricSchaefer; 14.11.2008
comment
Заморозки не произошло, когда я сделал то, что вы предложили. Поэтому я изменил все Invoke() на BeginInvoke(). - person EricSchaefer; 05.12.2008

Наиболее вероятный ответ (тупик) уже был предложен.

Другой способ имитировать такое поведение — уменьшить количество потоков пула и портов завершения ввода-вывода; вы случайно не звонили ThreadPool.SetMaxThreads(...)?

person Marc Gravell    schedule 13.11.2008
comment
№ (нужно не менее 10 символов для комментариев...) - person EricSchaefer; 13.11.2008