PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [VC++/MFC]in Thread auf Schließen eines Fensters warten - wie geht das?


Imperator Katarn
2006-01-24, 10:17:35
Hallo,

ich möchte in einem Programm (VC++/MFC) einen Worker-Thread (also kein GUI-Thread) darauf warten lassen, daß ein Dialogfenster geschlossen wird. Gibt es eine Möglichkeit, das elegant zu lösen? Ginge es z.B. mit WaitForSingleObject (aufzurufen im Thread)?

Meine bisherige Idee war, in dem Thread eine Schleife laufen zu lassen, in der fortwährend abgefragt wird, ob die CWnd-Instanz des Dialogfensters noch existiert, und die Schleife zu beenden sobald die Instanz nicht mehr existiert. Allerdings keine sehr schöne Lösung, wie ich finde.

Der Hintergrund ist folgender: ich habe ein Dialogfenster CParentDlg, sowie ein zweites, nicht-modales Dialogfenster CChildDlg, das ein Child von CParentDlg ist. CChildDlg läßt während seiner gesamten Lebensdauer einen Worker-Thread CChildDlg::ThreadDoWork laufen.
Nun soll es möglich sein, durch Klick auf den Exit-Button von CParentDlg die folgende Ereigniskette auszulösen: der Thread CChildDlg::ThreadDoWork wird beendet, dann wird der Dialog CChildDlg geschlossen, und dann CParentDlg geschlossen.

Das Beenden von CChildDlg::ThreadDoWork und das darauffolgende Schließen von CChildDlg habe ich durch einen Cleanup-Thread CChildDlg::ThreadCleanup realisiert, der auf eine Nachricht vom Parent-Dialog hin gestartet wird und dann dem Thread CChildDlg::ThreadDoWork signalisiert, daß der sich beenden soll, darauf wartet (mittels WaitForSingleObject) daß dies geschieht (was länger dauern kann) und anschließend das Schließen des Child-Dialoges auslöst.
Analog wollte ich in CParentDlg einen Thread CParentDlg::ThreadCleanup einbauen, der den Cleanup-Thread von CChildDlg auslöst und dann wartet bis CChildDlg geschlossen wird.

Und genau um diesen Thread CParentDlg::ThreadCleanup geht es in meiner Frage: wie kann ich das elegant realisieren, daß dort auf das Schließen des Child-Dialoges (respektive die Destruktion der CChildDlg-Instanz) gewartet wird?

Hier ein Sourcecode-Beispiel:

class CChildDlg : public CDialog
{
//...
static UINT ThreadDoWork(LPVOID pParam);
static UINT ThreadCleanup(LPVOID pParam);
};

class CParentDlg : public CDialog
{
//...
CChildDlg* m_pChildDlg;
static UINT ThreadCleanup(LPVOID pParam);
};


static UINT CParentDlg::ThreadCleanup(LPVOID pParam)
{
// pParam konvertieren
CParentDlg* pThisDlg = (CParentDlg*) pParam;

// Nachricht an CChild-Instanz senden
pThisDlg->m_pChildDlg->SendMessage(/* bitte beende dich */);

// warten bis Child-Dialog geschlossen ist
// bisherige (unschöne) Lösung:
// alle 50 ms abfragen ob CChildDlg-Instanz noch existiert:
while (pThisDlg->m_pChildDlg)
{
Sleep(50);
}

//...
}


Thx for your help :)

muhkuh_rs
2006-01-24, 11:04:13
Ich würde es so machen:
1. Wenn der Parentdialog geschlossen wird, den Child Dialog schließen. Dann wird dessen OnClose Funktion (oder so ähnlich aufgerufen).
2. In CChildDialog::OnClose dem Thread zu verstehen geben, das er sich beenden soll und darauf warten, dass das passiert ist, mit polling oder WaitForSingleObjhect ...
3. Wenn die OnClose Funktion ausläuft, ist alles gestoppt und die Anwendung kann terminieren.

Wenn das Beenden des worker Threads wirklich lange dauert, dann ist die Anwendung zwar für diese Zeit blockiert, was aber nicht wirklich ein Problem sein sollte, da sie ja soweiso geschlossen werden soll.

Imperator Katarn
2006-01-24, 11:29:42
Wenn das Beenden des worker Threads wirklich lange dauert, dann ist die Anwendung zwar für diese Zeit blockiert, was aber nicht wirklich ein Problem sein sollte, da sie ja soweiso geschlossen werden soll.doch, das ist ein Problem, weil mit dem Schließen des Parent-Dialoges die Anwendung noch gar nicht beendet wird. Aber unabhängig davon wäre es schlicht unschön, wenn der Parent-Dialog nach dem Klick auf Schließen noch eine Weile blockiert.

muhkuh_rs
2006-01-24, 11:52:39
doch, das ist ein Problem, weil mit dem Schließen des Parent-Dialoges die Anwendung noch gar nicht beendet wird. Aber unabhängig davon wäre es schlicht unschön, wenn der Parent-Dialog nach dem Klick auf Schließen noch eine Weile blockiert.

Edit: Also ist der Parent Dialog nicht das Anwendungsfenster?

Es wäre deswegen vielleicht einfach besser dafür zu sorgen, dass der worker thread öfter nachschaut, ob er noch laufen soll. Dann hängt auch nix.

Wenn das aus irgendwelchen Gründen nicht geht, dann kann der Child Dialog eine Nachricht an den Parent Dialog posten wenn er sich schließt.

Imperator Katarn
2006-01-24, 12:46:22
So wie ich das verstehe blockiert die Anwendung doch sowieso. Es wird gewartet, bis der Thread beendet wird, ob nun im GUI Thread order in einem eigenen. Der User kann da wohl nichts sinnvolles mehr machen oder? deswegen muß man ihm aber kein gefreeztes Dialogfenster vor die Nase halten, das noch da ist obwohl er gerade dessen Exit-Button geklickt hat. Es gibt hier mehr um Ästhetik als um Funktionalität.

Zwar könnte man das Fenster schon mal verstecken, jedoch wäre das noch schlimmer, da der Nutzer dann nicht weiß, dass das Programm noch läuft.du meinst der Thread. Das braucht der User aber auch nicht zu wissen, da er in der Zeit die bis zum Beenden des Threads vergeht, noch nicht dazu kommt weitere Aktionen anzustoßen. Es geht wie gesagt um die Optik: der User klickt auf den Exit-Button des Parent-Dialoges und sieht sogleich beide Dialoge zugehen, statt daß sie noch eine Sekunde oder länger gefreezt angezeigt werden.

Es wäre deswegen vielleicht einfach besser dafür zu sorgen, dass der worker thread öfter nachschaut, ob er noch laufen soll. Dann hängt auch nix.was daran scheitert, daß es schlicht nicht möglich ist, was insbesondere daran liegt, daß CChild::ThreadDoWork externe Geräte steuert.

Imperator Katarn
2006-01-24, 13:14:19
ich habe jetzt eine Lösung mittels WaitForSingleObject gefunden: ich füge in CParentDlg einen Member m_hCloseChildEvent vom Typ HANDLE ein. In CParentDlg::ThreadCleanup rufe ich dann CreateEvent und lasse den Thread warten bis m_hCloseChildEvent auf signaled gesetzt wird, was dadurch erreicht wird, daß der Child-Dialog eine Nachricht an den Parent-Dialog sendet wenn er bereit ist geschlossen zu werden:

class CChildDlg : public CDialog
{
//...
static UINT ThreadDoWork(LPVOID pParam);
static UINT ThreadCleanup(LPVOID pParam);
};

class CParentDlg : public CDialog
{
//...
CChildDlg* m_pChildDlg;
// Event-Handle:
HANDLE m_hCloseChildEvent;
static UINT ThreadCleanup(LPVOID pParam);
// verarbeitet Nachricht vom Child-Dialog:
long OnChildClose(WPARAM wParam, LPARAM lParam);
};


static UINT CParentDlg::ThreadCleanup(LPVOID pParam)
{
// pParam konvertieren
CParentDlg* pThisDlg = (CParentDlg*) pParam;

// Event erzeugen
m_hCloseChildEvent = CreateEvent(NULL, TRUE, FALSE /*Event ist zunächst non-signaled */, "CloseChildEvent");

// Nachricht an CChild-Instanz senden
pThisDlg->m_pChildDlg->SendMessage(/* bitte beende dich */);

// warten bis Child-Dialog geschlossen ist
WaitForSingleObject(m_hCloseChildEvent, INFINITE);

CloseHandle(m_hCloseChildEvent);

//...
}

/* wird durch Nachricht vom Child-Dialog aufgerufen */
long CParentDlg::OnCloseChild(WPARAM wParam, LPARAM lParam)
{
// Child-Dialog schließen
m_pChildDlg->ShowWindow(SW_HIDE);
m_pChildDlg->DestroyWindow();
delete m_pChildDlg;
m_pChildDlg = NULL;

// Event auslösen
SetEvent(m_hCloseChildEvent);

return 0;
}

muhkuh_rs
2006-01-24, 14:38:09
Ups, da hab ich wohl zu spät editiert. Ich finde den Aufwand jedenfalls ziemlich hoch. Ich würde versuchen, beim Beenden des ChildDialog mit PostMessage dem ParentDialog zu signalisieren, dass er beendet worden ist.

Ich meine nur um einen Thread sauber zu beenden, machst du zwei neue auf. Geht zwar, ist aber nicht wirklich schick. Erlaubt denn die API des externen Gerätes keinen Abbruch von blockenden Funktionen?

Imperator Katarn
2006-01-24, 15:18:48
Ups, da hab ich wohl zu spät editiert. Ich finde den Aufwand jedenfalls ziemlich hoch. Ich würde versuchen, beim Beenden des ChildDialog mit PostMessage dem ParentDialog zu signalisieren, dass er beendet worden ist.

Ich meine nur um einen Thread sauber zu beenden, machst du zwei neue auf. Geht zwar, ist aber nicht wirklich schick. das mit dem PostMessage hatte ich zuvor auch schon versucht. Ich hatte dazu dem CParentDlg einen Member m_bExitDlgSignal verpaßt, damit der sich, wenn die Nachricht vom Child-Dialog reinkommt, erinnert daß er sich schließen sollte. Bei der Weiterentwicklung des Parent-Dialoges, der auch noch andere Dinge tut und u.a. auch noch weitere Worker-Threads anlegt, erschien mir dieses Signalisierungssystem aber zunehmend kompliziert und der Codewartbarkeit alles andere als zuträglich.
Das Konzept der zwei Cleanup-Threads finde ich einfach besser durchschaubar und ausbaufähiger.

Erlaubt denn die API des externen Gerätes keinen Abbruch von blockenden Funktionen?nein.