2 Stimmen

MFC/WIN32/C++: Wie kann man Nachrichten versenden, während man eine rechenintensive Operation durchführt?

Ich würde es vorziehen, zu sehen, wenn es einen vernünftigen Weg, dies zu behandeln, ohne einen zweiten Thread aufgrund der Besonderheiten dieser Anwendung (ursprünglich eine DOS-Anwendung mit vielen globalen statischen Variablen, konvertiert zu Windows/MFC, aber nicht von Grund auf entworfen, um Multi-Thread sein - nur machen es mehrere Dokumente bewusst war ein großes Unternehmen).

Die fragliche Anwendung versucht, eine sehr rechenintensive Operation durchzuführen, die den Nebeneffekt hat, dass das aktuelle Dokument geändert wird. Sie möchte in der Lage sein, das Hauptfenster und die Dokumentfenster iterativ zu aktualisieren, während der Prozess durchgeführt wird.

Der einfache Ansatz: Schleife durch die Arbeitselemente, Zeichnen auf dem Dokumentfenster, Ändern der zugrunde liegenden Daten des Dokuments und Aktualisieren der Statusleiste mit einem Statustext, der angibt, dass x von y abgeschlossen ist, funktioniert normalerweise. Aber manchmal hält die Anwendung die Aktualisierung der Statusleiste und des Dokumentfensters (Ansicht) an, bis der gesamte Auftrag abgeschlossen ist, und dann wird alles auf einmal aktualisiert.

Es gibt also keine Hänge-Bedingungen im Code, d.h. er wird nie nicht abgeschlossen. Es geht lediglich darum, dass Fenstermeldungen während der gesamten Dauer nicht verarbeitet werden (da es sich größtenteils um eine Anwendung mit nur einem Thread handelt).

Ich dachte, der offensichtliche Ansatz wäre, eine PeekMessage()-Schleife innerhalb der Task-Schleife zu platzieren, um alle gesammelten Nachrichten zu versenden. Ich nahm an, dass dies der Grund dafür ist, dass die visuellen Aktualisierungen nicht mehr stattfinden: Es muss eine Windows-Nachricht entweder in der Nachrichtenwarteschlange der Ansicht, des Mainframes oder des Threads sein, die die direkten Aktualisierungen des Bildschirms blockiert.

Vielleicht liegt das Problem aber auch woanders?

Unabhängig davon scheint es eine schlechte Idee zu sein, dass unsere Anwendung die Nachrichtenwarteschlange ignoriert, da dies Äonen von Verarbeitungszeit in Anspruch nehmen kann (der Benutzer wird die Anwendung als "nicht mehr reagierend" wahrnehmen, wenn er den Task-Manager fragt, nur weil unsere Nachrichtenwarteschlange nicht verarbeitet wird).

Die folgende Schleife wird jedoch unendlich:

    // 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;
    }

HINWEIS: TraceMessage() gibt einfach einen menschenfreundlichen Trace in das Debugger-Fenster aus, um welche Nachricht und Argumente es sich handelt. In diesem Fall ist es WM_PAINT.

Die Farbmeldung scheint also ewig in der Warteschlange zu verweilen (oder es werden unendlich viele neue Meldungen generiert), obwohl sie eigentlich entfernt werden sollte.


Fragen:

  1. Könnte es sein, dass ich mich irre und der Grund, warum unsere Anwendungen die Statusleiste und die Ansicht nicht mehr aktualisieren können, ein anderer ist?

  2. Gibt es einen besseren Ansatz für eine lange CPU oder Festplatte I/o-intensive Operation als Platzierung einer PeekMessage-Schleife innerhalb der Task-Schleife (die nicht die gesamte Anwendung neu zu architektieren, um mehr Multi-Thread-freundlich sein)?


Die Lösung, die ich anstrebe...

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();
}

Ja, das ist eine sehr alte Technologie und nicht unbedingt der beste Ansatz. Sie ist jedoch machbar und für diesen Kontext sehr nützlich. Wir haben bereits eine komplexe Anwendung, die die OnIdle-Mechanik für eine Vielzahl von Zwecken nutzt, was sie als möglichen Ansatz weniger attraktiv macht.

3voto

Blindy Punkte 59463

PeekMessage blickt nur auf die nächste Nachricht voraus. Sie wollen GetMessage um sie aus der Warteschlange zu entfernen, und dann DispatchMessage um tatsächlich aufzurufen WndProc .

An diesem Punkt haben Sie gerade eine neue DoEvents von VB, dem Grundnahrungsmittel der Technologie von 1996.

2voto

Mark Ransom Punkte 283960

Der Grund, warum Ihre Anwendung nicht aktualisiert wird, ist, dass die WM_PAINT-Meldungen nicht verarbeitet werden. WM_PAINT ist insofern einzigartig, als es bei Bedarf generiert wird, wenn Windows feststellt, dass Teile eines Fensters nicht mehr aktuell sind. Deshalb erhalten Sie eine unendliche Anzahl von ihnen.

Die beste Lösung besteht darin, eine lange Aufgabe in mehrere Teile aufzuteilen und mit PostMessage eine benutzerdefinierte Nachricht in die Warteschlange zu stellen, mit der Sie dort weitermachen können, wo Sie aufgehört haben.

2voto

Jerry Coffin Punkte 452852

Andere haben bereits darauf hingewiesen, dass WM_PAINT normalerweise synthetisiert wird, wenn es benötigt wird, und nicht, wenn tatsächlich eine WM_PAINT-Nachricht in der Nachrichtenwarteschlange steht.

Um zu Ihrem eigentlichen Problem zu kommen, gibt es meines Erachtens nur zwei Möglichkeiten: Entweder Sie bauen das System um oder Sie lassen es bleiben. Wenn Sie es in Ruhe lassen, ist es wahrscheinlich am besten, es einfach zu isolieren, so dass Ihre Anwendung in zwei fast vollständig getrennten Teilen läuft: ein Teil ist Ihre fast unveränderte DOS-Anwendung, die alles so macht, wie sie es immer gemacht hat. Das andere ist ein GUI-Frontend, das fast vollständig getrennt ist.

Wie Sie das genau machen, hängt davon ab, wie die DOS-Anwendung ihre Ausgabe erzeugt. Wenn sie Standardstreams verwendet (z.B. Schreiben auf die Standardausgabe mit printf und so), ist es wahrscheinlich am einfachsten, sie in eine Win32-Konsolenanwendung zu konvertieren. Lassen Sie Ihre GUI-Anwendung die Konsolenanwendung starten und leiten Sie deren Standardeingabe-, Standardausgabe- und Standardfehlerströme in anonyme Pipes zur Elternanwendung um. Die übergeordnete GUI-Anwendung liest dann (zum Beispiel) die Daten, die auf der Standardausgabe des Childs eintreffen, und aktualisiert die GUI entsprechend.

Wenn Sie jedoch ein "Vollbild"-DOS-Programm haben, das geschrieben wurde, um den Bildschirmpuffer direkt zu nutzen, dann ist es etwas einfacher, es als Teil derselben Anwendung in der grafischen Benutzeroberfläche zu behalten. In einem typischen Fall wird ein solches Programm einen Code enthalten, der einen Zeiger auf den Bildschirm abruft und diesen dann als 2D-Array von Zeichen/Attribut-Paaren behandelt. Um die Ausgabe "einzufangen", ersetzen Sie den Hardware-Bildschirmpuffer durch ein eigenes Array. Von dort aus haben Sie zwei Möglichkeiten. Wenn der ursprüngliche Code in C++ (oder C, das mit C++ kompatibel ist) geschrieben ist, kann Ihr virtueller Bildschirmpuffer einige Operatoren überladen, um Ihnen mitzuteilen, dass Änderungen vorgenommen werden sollen. Andernfalls können Sie ihn alle (sagen wir) 100 ms abfragen und (z. B.) einen Hash des Inhalts durchführen, um festzustellen, ob er sich geändert hat, so dass Sie Ihre GUI aktualisieren müssen. Während das Polling niemals klingt eine gute Idee ist, ist es unwahrscheinlich, dass das Hashing von 8K Daten in 100 ms-Intervallen ein großes Problem darstellt.

Ich wiederhole jedoch: Es gibt, zumindest IMO, nur zwei Möglichkeiten, die wahrscheinlich gut funktionieren werden: Entweder man integriert den Code sauber, indem man ihn so umgestaltet, dass er gut in einem Thread läuft, oder man trennt den Code sauber, indem man eine saubere Kommunikation zwischen der Berechnung und der grafischen Benutzeroberfläche herstellt. Zumindest IME, auf halbem Weg zwischen diesen Extremen selten gut funktionieren. Sie müssen den Code entweder trennen oder integrieren, aber in jedem Fall müssen Sie es gründlich und sauber machen.

1voto

Ben Voigt Punkte 268424

WM_PAINT wird immer dann synthetisiert, wenn die Warteschlange leer ist und es eine nicht leere ungültige Region gibt. Zum Anhalten WM_PAINT Nachrichten, müssen Sie den Fensterbereich als gültig markieren. Dies geschieht während BeginPaint / EndPaint im normalen Verlauf der Farbverarbeitung.

Wenn die Dokumentation Wenn es heißt: "Nachdem eine interne WM_PAINT-Nachricht von GetMessage oder PeekMessage zurückgegeben oder von UpdateWindow an ein Fenster gesendet wurde, werden keine weiteren WM_PAINT-Nachrichten gesendet, bis das Fenster für ungültig erklärt wird oder bis RedrawWindow erneut mit gesetztem RDW_INTERNALPAINT-Flag aufgerufen wird", wird davon ausgegangen, dass die Nachricht vollständig verarbeitet wurde. Wenn Sie sich nicht an den Vertrag von WM_PAINT Verarbeitung (Aufruf BeginPaint y EndPaint (u.a.), dann kann man sich auch nicht auf das Verhalten "keine WM_PAINT-Meldungen mehr" verlassen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X