// recaldoc.cpp : implementation of the CRecalcDoc class // // This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #include "mtrecalc.h" #include "calcthrd.h" #include "recaldoc.h" #include "slowcalc.h" #include "speeddlg.h" #include "recalcvw.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc IMPLEMENT_DYNCREATE(CRecalcDoc, CDocument) BEGIN_MESSAGE_MAP(CRecalcDoc, CDocument) ON_COMMAND_RANGE(ID_SINGLE_THREAD, ID_UI_THREAD, OnDemoThread) ON_UPDATE_COMMAND_UI_RANGE(ID_SINGLE_THREAD, ID_UI_THREAD, OnUpdateDemoThread) //{{AFX_MSG_MAP(CRecalcDoc) ON_COMMAND(ID_RECALC_SPEED, OnRecalcSpeed) ON_COMMAND(ID_KILL_WORKER_THREAD, OnKillWorkerThread) ON_UPDATE_COMMAND_UI(ID_KILL_WORKER_THREAD, OnUpdateKillWorkerThread) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc construction/destruction CRecalcDoc::CRecalcDoc() { m_nInt1 = m_nInt2 = m_nSum = 0; m_bRecalcNeeded = m_bRecalcInProgress = FALSE; m_nRecalcSpeedSeconds = 5; m_nCurrentDemoCommand = ID_SINGLE_THREAD; m_hEventStartRecalc = CreateEvent(NULL, FALSE, FALSE, NULL); // auto reset, initially reset m_hEventRecalcDone = CreateEvent(NULL, TRUE, TRUE, NULL); // manual reset, initially set m_hEventKillRecalcThread = CreateEvent(NULL, FALSE, FALSE, NULL); // auto reset, initially reset m_hEventRecalcThreadKilled = CreateEvent(NULL, FALSE, FALSE, NULL); // auto reset, initially reset m_recalcThreadInfo.m_hEventStartRecalc = m_hEventStartRecalc; m_recalcThreadInfo.m_hEventRecalcDone = m_hEventRecalcDone; m_recalcThreadInfo.m_hEventKillRecalcThread = m_hEventKillRecalcThread; m_recalcThreadInfo.m_hEventRecalcThreadKilled = m_hEventRecalcThreadKilled; m_pRecalcWorkerThread = NULL; } CRecalcDoc::~CRecalcDoc() { // In this application, the document owns the worker thread. // The document's destructor is responsible for killing the active worker // thread. // It's a good idea to wait for the worker thread to notify via a // "thread killed" event that it has killed itself. Otherwise, in the case // where the app is terminating, is possible (even if unlikely) that it // will detect a memory leak of the CWinThread object before the // CWinThread object has had a chance to auto-delete itself. DWORD dwExitCode; if (m_pRecalcWorkerThread != NULL && GetExitCodeThread(m_pRecalcWorkerThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE) { // Kill the worker thread by setting the "kill thread" event. // See comment in OnKillWorkerThread for explanation of the sequence // of the "kill thread" and "start recalc" events. SetEvent(m_hEventKillRecalcThread); SetEvent(m_hEventStartRecalc); WaitForSingleObject(m_hEventRecalcThreadKilled, INFINITE); } } ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc overrides BOOL CRecalcDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; return TRUE; } BOOL CRecalcDoc::OnSaveDocument(LPCTSTR lpszPathName) { if (m_bRecalcInProgress) { if (AfxMessageBox(IDS_CONTINUE_RECALC_BEFORE_SAVE, MB_YESNO) == IDYES) { BeginWaitCursor(); WaitForSingleObject(m_hEventRecalcDone, INFINITE); RecalcDone(); EndWaitCursor(); } else { AfxMessageBox(IDS_FILE_NOT_SAVED); return FALSE; } } else if (m_bRecalcNeeded) // recalc needed but not in progress { if (AfxMessageBox(IDS_NEED_RECALC_BEFORE_SAVE, MB_YESNO) == IDYES) { BeginWaitCursor(); // Update the document from data in the view and start recalculation. POSITION pos = GetFirstViewPosition(); ASSERT(pos != NULL); CView* pView = GetNextView(pos); pView->SendMessage(WM_USER_RECALC_NOW, 0, 0); WaitForSingleObject(m_hEventRecalcDone, INFINITE); RecalcDone(); EndWaitCursor(); } else { AfxMessageBox(IDS_FILE_NOT_SAVED); return FALSE; } } return CDocument::OnSaveDocument(lpszPathName); } ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc serialization void CRecalcDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << (WORD)m_nInt1; ar << (WORD)m_nInt2; ar << (WORD)m_nSum; } else { WORD w; ar >> w; m_nInt1 = w; ar >> w; m_nInt2 = w; ar >> w; m_nSum = w; } } ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc diagnostics #ifdef _DEBUG void CRecalcDoc::AssertValid() const { CDocument::AssertValid(); } void CRecalcDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc operations and implementation void CRecalcDoc::UpdateInt1AndInt2(int n1, int n2, BOOL bForceRecalc) { if (!bForceRecalc && m_nInt1 == n1 && m_nInt2 == n2) return; // recalculation not needed SetModifiedFlag(); m_nInt1 = n1; m_nInt2 = n2; m_bRecalcNeeded = m_bRecalcInProgress = TRUE; // Synchronize the int1 and int2 fields in the other views, // and show "recalculating..." in the sum field. UpdateAllViews(NULL); switch (m_nCurrentDemoCommand) { case ID_SINGLE_THREAD: RecalcInSingleThread(); break; case ID_WORKER_THREAD: RecalcInSecondThread(); break; default: ASSERT(FALSE); } } void CRecalcDoc::RecalcInSingleThread() { SlowAdd(m_nInt1, m_nInt2, m_nSum, NULL, m_nRecalcSpeedSeconds, AfxGetMainWnd()->m_hWnd); m_bRecalcNeeded = m_bRecalcInProgress = FALSE; // Update the "sum" field. UpdateAllViews(NULL, UPDATE_HINT_SUM); } void CRecalcDoc::RecalcInSecondThread() { if (m_pRecalcWorkerThread == NULL) { // Begin the worker thread. It is ok to fill in the CThreadInfo // structure after the thread has started, because the thread // waits for the "start recalc" event before referring to the structure. m_pRecalcWorkerThread = AfxBeginThread(RecalcThreadProc, &m_recalcThreadInfo); } m_recalcThreadInfo.m_nInt1 = m_nInt1; m_recalcThreadInfo.m_nInt2 = m_nInt2; POSITION pos = GetFirstViewPosition(); ASSERT(pos != NULL); CView* pView = GetNextView(pos); ASSERT(pView != NULL); m_recalcThreadInfo.m_hwndNotifyRecalcDone = pView->m_hWnd; m_recalcThreadInfo.m_hwndNotifyProgress = AfxGetMainWnd()->m_hWnd; m_recalcThreadInfo.m_nRecalcSpeedSeconds = m_nRecalcSpeedSeconds; // The events are initially set or reset in the CreateEvent call; // but they may be left in an improperly initialized state if // a worker thread has been previously started and then prematurely // killed. Set/reset the events to the proper initial state. // Set the "start recalc" event last, since it is the event the // triggers the starting of the worker thread recalculation. SetEvent(m_hEventRecalcDone); ResetEvent(m_hEventKillRecalcThread); ResetEvent(m_hEventRecalcThreadKilled); SetEvent(m_hEventStartRecalc); // RecalcDone() will be called by the view when the thread sends a // WM_USER_RECALC_DONE message. } void CRecalcDoc::RecalcDone() { // Called by the view when the worker thread sends a WM_USER_RECALC_DONE message. m_nSum = m_recalcThreadInfo.m_nSum; m_bRecalcNeeded = m_bRecalcInProgress = FALSE; UpdateAllViews(NULL, UPDATE_HINT_SUM); } ///////////////////////////////////////////////////////////////////////////// // CRecalcDoc commands void CRecalcDoc::OnDemoThread(UINT nCmdID) { m_nCurrentDemoCommand = nCmdID; } void CRecalcDoc::OnUpdateDemoThread(CCmdUI* pCmdUI) { pCmdUI->SetCheck(pCmdUI->m_nID == m_nCurrentDemoCommand); } void CRecalcDoc::OnRecalcSpeed() { CSpeedDlg dlg; dlg.m_nRecalcSpeedSeconds = m_nRecalcSpeedSeconds; if (dlg.DoModal() == IDOK) m_nRecalcSpeedSeconds = dlg.m_nRecalcSpeedSeconds; } void CRecalcDoc::OnKillWorkerThread() { // The worker thread periodically checks for the "kill recalc" event // during its execution of a recalculation. When the worker thread // is not currently recalculating, it is waiting for a "start recalc" // event. Therefore, in order to kill the thread that might be waiting // for the "start recalc" event, it is necessary to first set the // "start recalc" event. SetEvent(m_hEventKillRecalcThread); SetEvent(m_hEventStartRecalc); WaitForSingleObject(m_hEventRecalcThreadKilled, INFINITE); m_pRecalcWorkerThread = NULL; m_bRecalcInProgress = FALSE; // but m_bRecalcNeeded is still TRUE UpdateAllViews(NULL, UPDATE_HINT_SUM); } void CRecalcDoc::OnUpdateKillWorkerThread(CCmdUI* pCmdUI) { pCmdUI->Enable(m_nCurrentDemoCommand == ID_WORKER_THREAD && m_pRecalcWorkerThread != NULL); }