Цикл сообщений WinForms не отвечает - PullRequest
7 голосов
/ 22 июля 2011

Я намеренно злоупотребляю циклом сообщений в приложении Windows Forms, но мой проект «просто для удовольствия» быстро вышел за пределы моего уровня понимания.Пока задание выполняется, форма не отвечает.Да, есть много других подобных вопросов, но в моем случае я намеренно избегаю работы над другим потоком (чтобы выиграть пари против себя?)

У меня есть функция, которая работает для (многих) коротких срезоввремени в потоке пользовательского интерфейса: get_IsComplete() проверяет, завершена ли задача;DoWork() зацикливается от 0 до 1000 (только для поддержания процессора в тепле).Задача запускается путем вызова control.BeginInvoke(new Action(ContinueWith), control);, после чего она (рекурсивно завершается) вызывает себя до завершения, всегда выполняя небольшой фрагмент работы в потоке пользовательского интерфейса.

public void ContinueWith(Control control)
{
    if (!IsComplete)
    {
        DoWork();
        OnNext(control);
        control.BeginInvoke(new Action(ContinueWith), control);
    }
    else
    {
        OnCompleted(control);
    }
}

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

Есть предложения?

Ответы [ 3 ]

17 голосов
/ 22 июля 2011

Вызов control.BeginInvoke () помещает передаваемый вами делегат во внутреннюю очередь и вызывает PostMessage (), чтобы пробудить цикл обработки сообщений и обратить внимание.Это то, что запускает первый BeginInvoke.Любые входные события (мышь и клавиатура) также попадают в очередь сообщений, Windows помещает их туда.

Поведение, на которое вы не рассчитывали, заключается в коде, который запускается при получении отправленного сообщения.Он не просто удаляет из очереди один запрос вызова и выполняет его, он зацикливается, пока вся очередь вызова не будет очищена.Как работает ваш код, эта очередь никогда не очищается, потому что вызов ContinueWith () добавляет еще один запрос вызова.Таким образом, он просто продолжает циклически обрабатывать запросы на вызовы и никогда не получает возможность получать больше сообщений из очереди сообщений.Или, другими словами: это прокачивает очередь вызовов, а не очередь сообщений.

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

Важно, чтобы она работала так, как работает, вызов PostMessage () не гарантированно работает.Windows не допускает более 10 000 сообщений в очереди сообщений.Но Control.BeginInvoke () не имеет такого ограничения.При полном опустошении очереди вызовов потерянное сообщение PostMessage не вызывает никаких проблем.Это поведение вызывает другие проблемы, хотя.Классический слишком часто вызывает BackgroundWorker.ReportProgress ().Такое же поведение, поток пользовательского интерфейса просто залит запросами вызова и больше не справляется со своими обычными обязанностями.Нахмурившись на любого, кто сталкивается с этим: «Я использую BackgroundWorker, но мой пользовательский интерфейс все еще зависает».

В любом случае, ваш эксперимент - ужасная ошибка.Вызов Application.DoEvents () потребуется для принудительного освобождения очереди сообщений.Много предостережений с этим, проверьте этот ответ для деталей.Предстоящая поддержка ключевого слова async предоставит еще один способ сделать это.Не уверен, что он по-другому обрабатывает приоритет сообщения.Я скорее сомневаюсь, что Control.BeginInvoke () довольно ядро.Одним из способов решения проблемы является использование таймера с очень коротким интервалом.Сообщения таймера также попадают в очередь сообщений (вроде), но имеют очень низкий приоритет.Входные события обрабатываются первыми.Или низкоуровневый хак: вызов PostMessage со своим собственным сообщением и переопределение WndProc для его обнаружения.Это становится немного прямолинейным и узким.Событие Application.Idle полезно для обработки после получения любых входных событий.

0 голосов
/ 22 июля 2011

Используйте перегрузку begininvoke, которая имеет приоритет. «Обычный» приоритет выше, чем ввод и рендеринг. Вам нужно выбрать что-то вроде applicationidle

0 голосов
/ 22 июля 2011

Попробуйте добавить вызов к

Application.DoEvents()

где-то в вашем методе ContinueWith ().

И, пожалуйста, не используйте такой стиль «потоков» ни для чего, кроме экспериментов. ;).

Источник: https://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents(v=vs.110).aspx

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