MFC / WIN32 / C ++: как отправлять сообщения, выполняя сложную вычислительную операцию? - PullRequest
2 голосов
/ 27 апреля 2011

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

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

Выполнение простого подхода: циклический просмотр рабочих элементов, выдача чертежа в окне документа, изменение базовых данных документа и обновление строки состояния с текстом статуса, указывающим x of y complete, обычно работает. Но иногда приложение перестает обновлять как строку состояния, так и окно документа (представление), пока все задание не будет завершено, а затем все обновляется сразу.

Так что в коде нет никаких зависаний. то есть оно никогда не завершается. Это просто вопрос невозможности обработки оконных сообщений в течение всего времени (так как это по большей части однопоточное приложение).

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

Однако, возможно, проблема в чем-то другом?

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

Однако следующий цикл становится бесконечным:

    // dispatch the messages until we're out of them again...
    for (MSG msg; PeekMessage(&msg, NULL,  0, 0, PM_REMOVE); )
    {
        TraceMessage(msg.message, msg.wParam, msg.lParam);
        if (msg.message == WM_QUIT)
            return;
    }

ПРИМЕЧАНИЕ: TraceMessage () просто выводит удобную для человека трассировку в окно отладчика, что это за сообщение и аргументы. В данном случае это WM_PAINT.

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


Вопросы:

  1. Могу ли я все неправильно понять, и причина, по которой наши приложения перестают обновлять строку состояния и вид, это что-то еще?

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


Решение, с которым я собираюсь ...

void DoSomethingLengthy()
{
    CWaitCursor wait;

    // disable our application windows much as if we were running a modal dialog
    // in order to lock out the user from interacting with us until we're doing doing this thing
    AfxGetMainWnd()->BeginModalState();

    while (bMoreWorkToDo)
    {
        // empty any generated / received thread & window messages
        for (MSG msg; PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE|PM_NOYIELD); ) 
            AfxPumpMessage();

        // for some reason, our cursor reverts to the normal pointer
        // so force ourselves to continue to have the wait-cursor until we're really done
        AfxGetMainWnd()->RestoreWaitCursor();


        // here's where we do one work-item
        // ...
    }

    // restore our prior state
    AfxGetMainWnd()->EndModalState();
}

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

Ответы [ 4 ]

3 голосов
/ 27 апреля 2011

PeekMessage смотрит вперед только на следующее сообщение. Вы хотите, чтобы GetMessage удалил его из очереди, а затем DispatchMessage фактически вызвал WndProc.

В этот момент вы только что заново изобрели DoEvents от VB, основного продукта технологии 1996 года.

2 голосов
/ 27 апреля 2011

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

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

Как именно вы это сделаете, зависит от того, как приложение DOS выдает свои результаты.Если он использует стандартные потоки (например, запись в стандартный вывод с помощью printf и т. Д.), То, вероятно, проще всего преобразовать его в консольное приложение Win32.Пусть ваше приложение с графическим интерфейсом порождает консольное приложение и перенаправляет его стандартный ввод, стандартный вывод и стандартные потоки ошибок в анонимные каналы к родительскому.Затем родительское приложение с графическим интерфейсом (например) будет считывать данные в том виде, в котором они поступают в стандартный вывод дочернего элемента, и соответствующим образом обновлять ваш графический интерфейс.

Если, OTOH, у вас есть "полноэкранная" DOS-программа, которая была написанаиспользовать экранный буфер напрямую, тогда его немного проще сохранить в графическом интерфейсе как часть того же приложения.В типичном случае такая программа будет иметь некоторый код, который получает указатель на экран, а затем обрабатывает его как двумерный массив пар символ / атрибут.Чтобы «перехватить» его вывод, вы заменяете собственный аппаратный буфер вместо массива собственным.Оттуда у вас есть два варианта.Если исходный код написан на C ++ (или C, который совместим с C ++), ваш буфер виртуального экрана может перегрузить некоторые операторы, чтобы уведомить вас о необходимости изменений.В противном случае вы можете опрашивать его каждые (скажем) 100 мс и (например) хэшировать содержимое, чтобы определить, изменилось ли оно, поэтому вам нужно обновить графический интерфейс.Хотя опрос никогда не звучит как хорошая идея, хеширование 8 КБ данных с интервалами в 100 мс вряд ли вызовет серьезную проблему.

Я повторю: хотя бы ИМО,есть только два варианта, которые могут сработать хорошо: либо аккуратно интегрировать код, перестроив его по мере необходимости, чтобы он хорошо работал в потоке, либо аккуратно разделить код, создав чистую связь между вычислениями и GUI,По крайней мере, IME, на полпути между этими крайностями редко работают хорошо.Вам нужно либо отделить, либо интегрировать код, но в любом случае вам нужно делать это тщательно и аккуратно.

2 голосов
/ 27 апреля 2011

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

Лучшее решение - разбить вашу длинную задачу на части и использовать PostMessage, чтобы поместить пользовательское сообщение в очередь, которую вы можете использовать, чтобы продолжить с того места, где вы остановились.

1 голос
/ 27 апреля 2011

WM_PAINT синтезируется всякий раз, когда очередь пуста и существует непустая недопустимая область.Чтобы остановить WM_PAINT сообщения, вы должны отметить область окна как действительную.Это происходит в течение BeginPaint / EndPaint при обычном процессе обработки краски.

Когда документация сообщает "После того, как внутреннее сообщение WM_PAINT возвращено из GetMessage или PeekMessage или отправлено вокно UpdateWindow, система не отправляет и не отправляет дальнейшие сообщения WM_PAINT, пока окно не станет недействительным или пока RedrawWindow не будет вызван снова с установленным флагом RDW_INTERNALPAINT. ", что предполагает, что сообщение было полностью обработано.Если вы не соблюдаете контракт на обработку WM_PAINT (среди прочего, вызовите BeginPaint и EndPaint), то на поведение «больше сообщений WM_PAINT» нельзя полагаться.

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