Zugriff auf nicht-modalen Dialog in Thread



  • Gegeben is folgendes:

    struct ThreadData
    { 
        CProgressDialog* pProgressDlg;
    };
    
    UINT ThreadProc(LPVOID lpParam);
    
    void foo() // im Hauptthread/Prozess
    {
        CProgressDialog* dlg = new CProgressDialog;
        dlg->Create(IDD_PROGRESSDIALOG);
        dlg->ShowWindow(SW_SHOW);
        ThreadData data = { dlg };
        CWinThread* pThread = AfxBeginThread(ThreadProc, &data);
        WaitForSingleObject(pThread->m_hThread, INFINITE);
    }
    
    UINT ThreadProc(LPVOID lpParam)
    {
        ThreadData* pData = static_cast<ThreadData*>(lpParam);
        ASSERT(pData); ASSERT(pData->pProgressDlg);
    
        // mache was, dabei wird pData->pProgressDlg->UpdateData(FALSE) aufgerufen,
        // der Text eines Editfeldes und der Fortschritt einer ProgressBar geändert 
        return 0;
    }
    

    Meine 2 Probleme sind:
    1. Assertion failed, weil man auf CWnd-Objekte in Threads nur über HWND zugreifen soll. Dann kann ich aber nicht mehr die Membervariablen meines Dialogs ändern. Ich müsste benutzerdefinierte Messages erstellen. Geht das nicht einfacher?
    2. Es wird nur der Dialog, nicht die Steuerelemente darauf, angezeigt. Außerdem reagiert der Dialog erst nach Beenden des Threads auf Benutzereingaben, was ja eigentlich wegen des Threads gerade nicht so sein soll! Liegt dies vielleicht am WaitForSingleObject im Hauptthread? Was kann ich dagegen tun?

    Danke!

    EDIT: Es ist übrigens Absicht, dass der CProgressDialog nicht automatisch geschlossen wird.

    [ Dieser Beitrag wurde am 14.04.2003 um 16:45 Uhr von Norondion editiert. ]



  • 1. Erstell den Dialog im Thread !
    2.

    ThreadData data = { dlg };
    CWinThread* pThread = AfxBeginThread(ThreadProc, &data);
    WaitForSingleObject(pThread->m_hThread, INFINITE);

    Das ist aber seeehr unschön und funktioniert nur, weil Deine Funktion vor ihrer Beendigung WaitForSingleObject stehen hat.

    P.S.
    WaitForSingleObject( INFINITE) hält den Thread an!! D.h. wenn Du das Fenster in Thread A erstellst und diesen anhälst, passiert logischerweise nix mehr, nicht einmal das Neuzeichnen!



  • 1. Leuchtet ein ;). Danke!
    2. Wie mache ich das besser?
    EDIT: Vielleicht, indem ich WaitForSingleObject einfach weglasse?

    EDIT_2: Gut, ich erzeuge ThreadData jetzt mit new und lasse das WaitForSingleObject. In meinem Thread wird aber CDocument::UpdateAllViews() aufgerufen, was der MFC nicht passt:

    // Note: if either of the above asserts fire and you are
    // writing a multithreaded application, it is likely that
    // you have passed a C++ object from one thread to another
    // and have used that object in a way that was not intended.
    // (only simple inline wrapper functions should be used)
    //
    // In general, CWnd objects should be passed by HWND from
    // one thread to another. The receiving thread can wrap
    // the HWND with a CWnd object by using CWnd::FromHandle.
    //
    // It is dangerous to pass C++ objects from one thread to
    // another, unless the objects are designed to be used in
    // such a manner.

    Hier kann ich aber nicht auf ein HWND ausweichen. Was muss ich stattdessen tun?

    [ Dieser Beitrag wurde am 14.04.2003 um 18:31 Uhr von Norondion editiert. ]



  • Kann man die Aufgabe nicht teilweise an den Hauptthread delegieren, indem man eine benutzerdefinierte Message an CDocument sendet (mit einem Zeiger auf eine Datenstruktur in lParam)?
    Wie geht sowas (Versenden der Nachricht, Ereignisbehandlungsroutine in CDocument)?



  • @Norondion:
    Zeig doch mal bissel Code. Zum Beispiel, welchen CWnd-Pointer du im Thread brauchst und so. Meinst Du das View?
    Ich sehe zum Beispiel nicht, von wo in dem Thread die Parameter herkommen sollen!

    Tip:
    Vor Erstellung des Threads eine Struktur anlegen, in der alle benötigten Werte gespeichert werden und diese an den Thread übergeben.



  • struct ThreadData
    {
        CEpistulaDoc* pDoc;
    };
    
    UINT ThreadProc(LPVOID lpParam);
    
    void foo()
    {
        CEpistulaDoc* pDoc = static_cast<CEpistulaDoc*>(static_cast<CMainFrame*>(AfxGetMainWnd())->GetActiveDocument());
        ThreadData* data= new ThreadData;
        data->pDoc = pDoc;
    
        AfxBeginThread(ThreadProc, data);
    }
    
    UINT ThreadProc(LPVOID lpParam)
    {
        ThreadData* pData = (ThreadData*) lpParam;
        ASSERT(pData); ASSERT_VALID(pData->pDoc);
    
        CProgressDialog* dlg = new CProgressDialog;
        dlg->Create(IDD_PROGRESSDIALOG);
        dlg->ShowWindow(SW_SHOW);
        for (int i=0; i<pData->pDoc->GetCount(); ++i) {
            //jetzt wird ein Element von pData->pDoc gelocked
            //d.h. es darf nicht mehr in den Views angezeigt werden
            //* dazu wird dann noch pData->pDoc->UpdateAllViews() aufgerufen
    
            //jetzt wird mit dem Element etwas gemacht
            //und dlg->m_ctlCurrentMailProgress.SetPos() aufgerufen
    
            //jetzt wird es unlocked (über pData->pDoc) 
            //* und dann wieder angezeigt (pData->pDoc->UpdateAllViews())
        }
        delete pData;
    
        return 0;
    }
    

    Die problematischen Stellen sind also die, bei denen pData->pDoc->UpdateAllViews() aufgerufen wird.

    [ Dieser Beitrag wurde am 15.04.2003 um 08:36 Uhr von Norondion editiert. ]



  • Kannst Du nicht die Daten vor der Thread-Erstellung locken, dann die Daten, durch die du im Thread iterierst, direkt an den Thread übergeben und dann im Dokument auf ein Signal vom Thread reagieren, welches besagt, dass der Thread beendet wurde und dann die Daten unlocken und die Views updaten?



  • Doch, aber wie sende ich ein Signal (CEvent?) vom Thread aus und wie bemerkt mein Dokument, dass dieses Signal gesetzt wurde?
    EDIT: auf http://www.codeproject.com/threads/usingworkerthreads.asp lese ich gerade

    You must not try to launch any GUI window from a worker thread. This means a worker thread cannot call on MessageBox, DoModal, Create of a modeless dialog, and so on. The only way you can handle this is to post a user-defined message back to the main GUI loop to perform this service for you.

    If you attempt to do this, you will get various odd failures. You will get ASSERT errors from MFC, dialogs will fail to come up, and essentially you will get all sorts of antisocial behavior from your application. If you really, really need to have GUI objects operating from a thread, you must not use a worker thread. You must use a user-interface thread, which has a message pump. I'm not an expert on these, although I've done a brief article on what I discovered about them while doing a SAPI application, and this may be of some assistance in using them.

    Ist mein new CProgressDialog also doch illegal?

    [ Dieser Beitrag wurde am 15.04.2003 um 08:53 Uhr von Norondion editiert. ]



  • Ist nicht illegal.
    Der Artikel beschreibt, dass bei jedem 'Versuch' der Hauptthread auf den Workerthread wartet, der Hauptthread also 'stehenbleibt' und keine Messages mehr verarbeitet. Dies hindert den Hauptthread natürlich auch daran, sich selber neu zu zeichnen, falls notwendig. Und genau das wollen wir vermeiden.
    Wenn man nämlich auf den Workerthread wartet, benutzt man dazu nicht WaitForSingleObject( INFINITE) wie in dem Artikel, sondern MsgWaitForMultipleObjects(), wie es in einem MSDN-Artikel beschrieben ist.
    Beispiel:

    CEvent event;
    void CMeinDocument::StartProcess()
    {
      ThreadData* data= new ThreadData;
      const int cObjects = 1;
      HANDLE phObjects[cObjects];
      phObjects[0] = (HANDLE)event;
      AfxBeginThread(ThreadProc, data);
    
      while (TRUE)
      {
        // block-local variable 
        DWORD result ; 
        MSG msg ; 
        // Read all of the messages in this next loop, 
        // removing each message as we read it.
        while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
        { 
          // If it's a quit message, we're out of here.
          if (msg.message == WM_QUIT)  
            return 1; 
          // Otherwise, dispatch the message.
          DispatchMessage(&msg); 
        } // End of PeekMessage while loop.
        // Wait for any message sent or posted to this queue 
        // or for one of the passed handles be set to signaled.
        result = MsgWaitForMultipleObjects( cObjects, phObjects, FALSE, INFINITE, QS_ALLINPUT); 
        // The result tells us the type of event we have.
        if (result == (WAIT_OBJECT_0 + cObjects))
        {
          // New messages have arrived. 
          // Continue to the top of the always while loop to 
          // dispatch them and resume waiting.
          continue;
        } 
        else 
        { 
          // One of the handles became signaled. 
          DoStuff (result - WAIT_OBJECT_0) ; 
          // UpdateAllViews();
        } // End of else clause.
      } // End of the always while loop. 
    }
    

    Wenn sich nun Dein Thread beendet, ist noch der Event mit SetEvent zu setzen.

    Damit umgehst du jede Blockierung.

    Eine zweite, einfachere aber nicht so effiziente Möglichkeit wäre, einen Timer einzusetzen, der in konstanten Abständen den Event abfragt und wenn er gesetzt wird, UpdateData aufruft und sich selber löscht. Auch diese Möglichkeit blockiert den Hauptthread nicht.



  • Danke! Ich probiere es gleich mal aus.


Anmelden zum Antworten