Forum: PC-Programmierung VC++ mit den MFC


von Alex (Gast)


Lesenswert?

Hallo,

ich stehe aktuell vor einem kleinen Problem, bei dem ich nicht so recht
weiterkomme. Vereinfacht gesagt habe ich ein GUI das einen Start- und
einen Stopp-Knopf hat. Mit einem Druck auf Start soll in einer
Endlosschleife etwas abgearbeitet werden, bis irgendwann mal wieder
Stopp gedrückt wird.

Wie löst man so etwas elegant (Threads wären ein Thema, aber eigentlich
scheue ich den Aufwand ...)?

Aktuell stehe ich ja vor dem Problem, dass bei einmal gedrücktem
Startknopf der Stoppknopf nicht mehr erkannt wird, da sich der PC in
einer Endlosschleife im Handler des Startbuttons befindet.

Eine Timerroutine (angestoßen durch Start, gestoppt durch Stopp) hilft
mir auch nicht wirklich, da die Dauer der Abarbeitung des Inhalts der
Schleife verschieden lang sein kann und der PC möglichst keine Zeit
verschenken soll.

Ich hoffe es ist halbwegs klar geworden, wo mein Problem liegt.


Alex

von Chris (Gast)


Lesenswert?

Entweder du nimmst Threads oder du sorgst dafür, dass innerhalb deiner
langen Schleife gelegentlich die anstehenden Nachrichten abgearbeitet
werden. Wie letzteres geht, weiß ich nicht (ich hab mir MFC nie
angetan), aber das sollte eine gängige Funktion sein, die man mit den
passenden Schlagwörtern sicher in der Doku findet.

von Hans (Gast)


Lesenswert?

das mit den msgs abarbeiten war ein PeekMessage()-DispatchMessage()
konstrukt.. was ich so in erinnerung hab... PeekMessage war auf jeden
fall drinnen...

wobei PeekMessage muss dann im prinzip das machen was
PreTranslateMessage macht.. nur eben nur für einen button (also auf ein
WM_COMMAND mit richtiger ID warten)

ob dieser kunstgriff einfacher ist wie ein thread... das bezweifle
ich..

weil dort hast du im einfachsten fall global ein CEvent und machst dann
bei start einen thread mit AfxBeginThread auf... da drinnen machst ein
while(WaitForSingleObject(....)== irgendwas_mit_timeout)

vom programmieraufwand wäre das einfacher und vor allem schneller...

wenn du noch mehr infos brauchst... einfach posten inwieweit du ahnung
von der mfc hast.. es ist sonst einfach mehr tipparbeit und wird
langatmig ;)

73

von Rufus T. Firefly (Gast)


Lesenswert?

.
  "Eine Timerroutine (angestoßen durch Start, gestoppt durch
  Stopp) hilft mir auch nicht wirklich, da die Dauer der
  Abarbeitung des Inhalts der Schleife verschieden lang sein
  kann und der PC möglichst keine Zeit verschenken soll."

Verstehe ich nicht so ganz. Dein langer Vorgang muss sich in mehrere
ausreichend kleine Vorgänge zerlegen lassen, und jedes Auftreten von
WM_TIMER löst einen dieser kleinen Vorgänge aus.

Das sieht im Grunde genommen in einem Thread, der über ein Event
synchronisiert wird, nicht anders aus; auch dort muss der lange Vorgang
in mehrere ausreichend kleine Vorgänge zerlegt werden und zwischen zwei
dieser kleinen Vorgängen das Event überprüft werden.

Im Thread auf das Event zu warten bedeutete, daß während dieser Zeit
der Thread nichts macht, was vermutlich kontraproduktiv ist.
Wird als Wartezeit 0 angegeben, wird zwar nicht gewartet, sondern das
Event nur gepollt, dann aber kann eben auch statt des Events einfach
ein Flag verwendet werden, das vom GUI-Thread gesetzt/gelöscht wird.

Es wäre allenfalls möglich, den Thread brutal zu beenden (per
TerminateThread), dann aber ist nicht sichergestellt, daß sich der in
einem definierten Zustand befindet, Dateihandles ordentlich geschlossen
sind etc.
Das empfiehlt sich daher nicht.

Um nähere auf das Problem eingehen zu können, sind mehr Informationen
über den langen Vorgang erforderlich; was ist das, was geschieht da?

von Hans (Gast)


Lesenswert?

huch rufus ich wusste garnicht das sich hier noch ein mfc/win32
geplagter tummelt ;)

die Idee mit TerminateThread empfiehlt sich schon deshalb nicht weil
folgendes in der msdn steht...

TerminateThread is a dangerous function that should only be used in
the most extreme cases. You should call TerminateThread only if you
know exactly what the target thread is doing, and you control all of
the code that the target thread could possibly be running at the time
of the termination. For example, TerminateThread can result in the
following problems:

If the target thread owns a critical section, the critical section will
not be released.
If the target thread is executing certain kernel32 calls when it is
terminated, the kernel32 state for the thread's process could be
inconsistent.
If the target thread is manipulating the global state of a shared DLL,
the state of the DLL could be destroyed, affecting other users of the
DLL.

sprich das ist eine subtil bösartige funktion die den thread ohne
rücksicht auf irgendwas vernichtet ;)

beim WaitForSingleObject das ich angesprochen hatte sollte ich
vielleicht noch anmerken, dass man es mit einem timeout von 0 aufruft..
das checkt dann nur ob das event gesetzt ist oder nicht...

flag gut und schön aber bitte dann wirklich nur einen atomaren datentyp
(sprich int, bool,...). alles andere ist wieder gefährlich...

CEvent ist schön und ist extra für sowas in der MFC drinnnen ;) also
warum nicht das verwenden ;)

zum thema thread sollte man dann noch anmerken, dass man sich den
thread handle oder das CWinThread objekt merken sollte...

dann kann man nämlcih entweder über den handle oder über das objekt
(autodelete auf false setzen in dem fall) schaun ob der thread noch da
ist...

man sollte nämlich mit dem beenden des programms warten bis alle
threads beendet sind...

73

von wgoedde (Gast)


Angehängte Dateien:

Lesenswert?

Hi,
könnte so aussehen...

von Alex (Gast)


Lesenswert?

Hallo,

erstmal vielen Dank für die Kommentare.


Habe die Sache mit einem Thread gelöst, war dann wohl doch das
einfachste.

Start-Button:
Setzt Flag = TRUE und startet Thread (AfxBeginThread (Thread, this));

Stopp-Button:
Löscht das Flag.

Die Routine Thread (vom Typ static) hat die Form:

UINT C...Dlg::Thread(LPVOID pParam)
{
  C...Dlg* pObj = (C...Dlg*) pParam;
  pObj -> Testfunktion ();
  return 1;
}

Die Funktion hat dann die Form:

unsigned int C...Dlg::Testfunktion (void)
{
  while (Flag_gesetzt)
  {
    //mache etwas bis Stopp
    //gedrückt wird
  }
}


Falls jemand nen einfacheren/besseren Weg kennt nur her mit den Ideen.

@Rufus
Die Funktion sendet ständig Kommandos an eine Steuerung
(Eigenentwicklung) und wertet die emfpangenen Daten aus. Um einen
realistischen Belastungstest machen zu können soll sie dies mit
maximaler Geschwindigkeit für einen relativ langen Zeitraum machen
(mehrere Stunden). Ein Timer wäre sicher eine Variante gewesen, dort
hätte ich dann aber mit massig Flags hantieren müssen, da der Anwender
aus einem großen Kommandovorrat auswählen kann. Ich denke, dass es so
einfacher ist.

Der Thread läuft so allerdings nach einmaligen Starten für immer, bis
die Applikation beendet wird. Dürfte aber kein Beinbruch sein?! :)

Alex

von Alex (Gast)


Lesenswert?

Nachtrag:

Das Beenden des Threads kann man so realisieren:

unsigned int C...Dlg::Testfunktion (void)
{
  while (Flag_gesetzt)
  {
    //mache etwas bis Stopp
    //gedrückt wird
  }
  AfxEndThread(NULL, TRUE);
}

Unterbricht der Stopp-Button die Endlosschleife beendet sich der Thread
selbst.

von Alex (Gast)


Lesenswert?

Hat jemand eine schlüssige Erklärung, warum man aus einem Thread heraus
nicht UpdateData (...) bzw. UpdateWindow (...) aufrufen kann?

Das führt jedesmal zu einem Absturz der Software.

Gibt es dafür ein Workaround?

von Rufus T. Firefly (Gast)


Lesenswert?

Soweit ich mich erinnere, darf das Handling von GUI-Dingen nur von
Threads ausgeführt werden, die über eine Message-Schleife verfügen.

Mehrere GUI-Threads sind möglich, aber extrem selten. Von einigen
Beispielen in Tutorials/Büchern abgesehen, habe ich noch keine reelle
Implementierung gesehen.
Üblich ist das Design mit mehreren "Worker-Thread", die GUI-los im
Hintergrund vor sich hinwerkeln und einem GUI-Thread.

Das ist auch gar nicht so furchtbar mies zu handhaben; ein
"Worker-Thread" kann mit PostThreadMessage eine Nachricht an einen
anderen Thread (hier also den GUI-Thread) posten.

Anstatt aus einem Thread heraus UpdateData/UpdateWindow aufzurufen,
würde ich eine Nachricht an den GUI-Thread posten, die dieser auswertet
und dann nötigenfalls UpdateData/Window aufruft.

Wenn irgendwelche Daten zwischen GUI-Thread und Worker-Threads
ausgetauscht werden, sollten Zugriffe auf diese diese durch geeignete
Verriegelungsmechanismen (CriticalSection) geschützt werden, da sonst
spannende Nebeneffekte auftreten können. Bei einfachen "atomaren"
Daten ist das nicht sehr kritisch, bei komplexen Datenstrukturen aber
(zu iterierende Listen o.ä.) wird die Fehlersuche dann recht spaßig.

von Alex (Gast)


Lesenswert?

Besten Dank für die Erleuchtung! Dann werde ich wohl ein paar Sachen
umschreiben müssen, macht aber nichts.


Alex

von Rufus T. Firefly (Gast)


Lesenswert?

Das passiert einem immer, wenn man anfängt, mit der MFC zu arbeiten. Die
ist halt ... anders. Man kann sich damit arrangieren, aber das braucht
einige Weile. Ich habe zu Anfang auch ziemlich übel geflucht, aber
damals (gut zehn Jahre her) gab es keine aussichtsreiche Alternative,
jedenfalls keine mir verfügbare.

Jedenfalls viel Erfolg!

von Marc Braun (Gast)


Lesenswert?

Eine etwas andere Methode ohne Threads, die ich gerne in Dialogen o.ä
verwende, ist das IdleTimeProcessing.

Unter folgendem Link kann man das Konzept hierfür nachlesen:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_idle_loop_processing.asp

Das Dokument ist auch Bestandteil der MSDN.

Die While-Schleife stellt hierbei die Endlos-Schleife dar und in dieser
Schleife kann deine Logik abgearbeitet werden.

Dies stellt aber nur bedingt eine Alternative zur Programmierung mit
Threads dar um kleinere Aufgaben, wie z.B. eine einfache Suche + Listen
füllen oder die Abfrage einer seriellen Schnittstelle zu
implementieren.

Gruß
Marc

von Alex (Gast)


Lesenswert?

@Marc

Das klingt auch nicht schlecht, allerdings ist OnIdle keine
Memberfunktion von CDialog. Wie also herankommen?

Sorry, aber OOP ist alles andere als mein Fachgebiet :-)

von Marc Braun (Gast)


Lesenswert?

@Alex
OnIdle ist eine Memberfunktion von CWinApp.

Daher
AfxGetApp()->OnIdle( ... );
AfxGetApp() liefert das CWinApp - Objekt der Applikation zurück.

Gruß
Marc

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.