Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 80 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
80
Dung lượng
885,13 KB
Nội dung
Doing Multiple Tasks at One Time––Multitasking 461 18 Creating the Main Thread Function Before you can spin off any independent threads, the thread must know what to do. You will create a main thread function to be executed by the thread when it starts. This func- tion will act as the main function for the thread, and the thread will end once the function ends. Therefore, this function must act as the primary control of the thread, keeping the thread running as long as there is work for the thread to do and then exiting once the thread’s work is completed. When you create a function to be used as the main function for a thread, you can pass a single parameter to this function. This parameter is a pointer to anything that contains all the information the thread needs to perform its tasks. For the application you’ve been building in this chapter, the parameter can be a pointer to the spinner that the thread will operate. Everything else that the thread needs can be extracted from the spinner object. Once the thread has a pointer to its spinner, it can get a pointer to the check box variable that tells it whether to continue spinning or stop itself. As long as the variable is TRUE, the thread should continue spinning. To add this function to your application, add a new member function to the document class in your application. Specify the function type as UINT, the function declaration as ThreadFunc(LPVOID pParam), and the access as private. You’ll start the thread from within the document class, so there’s no need for any other classes to see this function. Once you’ve added this function, edit it with the code in Listing 18.14. LISTING 18.14. THE CTaskingDoc ThreadFunc FUNCTION. 1: UINT CTaskingDoc::ThreadFunc(LPVOID pParam) 2: { 3: // Convert the argument to a pointer to the 4: // spinner for this thread 5: CSpinner* lpSpin = (CSpinner*)pParam; 6: // Get a pointer to the continuation flag 7: BOOL* pbContinue = lpSpin->GetContinue(); 8: 9: // Loop while the continue flag is true 10: while (*pbContinue) 11: // Spin the spinner 12: lpSpin->Draw(); 13: return 0; 14: } 024 31240-9 CH18 4/27/00 12:59 PM Page 461 Starting and Stopping the Threads Now that you have a function to call for the independent threads, you need some way of controlling the threads, starting and stopping them. You need to be able to hold onto a couple of pointers for CWinThread objects, which will encapsulate the threads. You’ll add these pointers as variables to the document object and then use them to capture the return variable from the AfxBeginThread function that you will use to start both of the threads. To add these variables to your application, add a new member variable to your document class. Specify the variable type as CWinThread*, the variable name as m_pSpinThread[2], and the variable access as private. This will provide you with a two slot array for holding these variables. Now that you have a place to hold the pointers to each of the two threads, you’ll add the functionality to start the threads. You can add a single function to start either thread, if it’s not currently running, or to wait for the thread to stop itself, if it is running. This function will need to know which thread to act on and whether to start or stop the thread. To add this functionality, add a new member function to the document class. Specify the function type as void, the function declaration as SuspendSpinner(int nIndex, BOOL bSuspend), and the function access as public, and check the Static check box. Edit the code for this function, adding the code in Listing 18.15. LISTING 18.15. THE CTaskingDoc SuspendSpinner FUNCTION. 1: void CTaskingDoc::SuspendSpinner(int nIndex, BOOL bSuspend) 2: { 3: // if suspending the thread 4: if (!bSuspend) 5: { 6: // Is the pointer for the thread valid? 7: if (m_pSpinThread[nIndex]) 8: { 9: // Get the handle for the thread 10: HANDLE hThread = m_pSpinThread[nIndex]->m_hThread; 11: // Wait for the thread to die 12: ::WaitForSingleObject (hThread, INFINITE); 13: } 14: } 15: else // We are running the thread 16: { 17: int iSpnr; 18: // Which spinner to use? 19: switch (nIndex) 20: { 21: case 0: 462 Day 18 024 31240-9 CH18 4/27/00 12:59 PM Page 462 Doing Multiple Tasks at One Time––Multitasking 463 18 22: iSpnr = 1; 23: break; 24: case 1: 25: iSpnr = 3; 26: break; 27: } 28: // Start the thread, passing a pointer to the spinner 29: m_pSpinThread[nIndex] = AfxBeginThread(ThreadFunc, 30: (LPVOID)&m_cSpin[iSpnr]); 31: } 32: } The first thing that you do in this function is check to see if the thread is being stopped or started. If the thread is being stopped, check to see if the pointer to the thread is valid. If the pointer is valid, you retrieve the handle for the thread by reading the value of the handle property of the CWinThread class: HANDLE hThread = m_pSpinThread[nIndex]->m_hThread; Once you have the handle, you use the handle to wait for the thread to stop itself with the WaitForSingleObject function. ::WaitForSingleObject (hThread, INFINITE); The WaitForSingleObject function is a Windows API function that tells the operating system you want to wait until the thread, whose handle you are passing, has stopped. The second argument to this function specifies how long you are willing to wait. By specify- ing INFINITE, you tell the operating system that you will wait forever, until this thread stops. If you specify a timeout value, and the thread does not stop by the time you specify, the function returns a value that indicates whether the thread has stopped. Because you specify INFINITE for the timeout period, you don’t need to worry about capturing the return value because this function does not return until the thread stops. If the thread is being started, you determine which spinner to use and then start that thread by calling the AfxBeginThread function. m_pSpinThread[nIndex] = AfxBeginThread(ThreadFunc, (LPVOID)&m_cSpin[iSpnr]); You passed the function to be called as the main function for the thread and the address of the spinner to be used by that thread. Triggering the Threads from the View Object Now that you have a means of starting and stopping each of the independent threads, you need to be able to trigger the starting and stopping from the check boxes on the window. 024 31240-9 CH18 4/27/00 12:59 PM Page 463 When each of the two check boxes is checked, you’ll start each of the threads. When the check boxes are unchecked, each of the threads must be stopped. The second part of this is easy: As long as the variable tied to the check box is kept in sync with the control, once the check box is unchecked, the thread will stop itself. However, when the check box is checked, you’ll need to call the document function that you just created to start the thread. To add this functionality to the first of the two thread check boxes, use the Class Wizard to add a function to the BN_CLICKED event for the check box. Once you add the function, edit it with the code in Listing 18.16. LISTING 18.16. THE CTaskingView OnCbthread1 FUNCTION. 1: void CTaskingView::OnCbthread1() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Sync the variables with the dialog 10: UpdateData(TRUE); 11: 12: // Get a pointer to the document 13: CTaskingDoc* pDocWnd = (CTaskingDoc*)GetDocument(); 14: // Did we get a valid pointer? 15: ASSERT_VALID(pDocWnd); 16: 17: // Suspend or start the spinner thread 18: pDocWnd->SuspendSpinner(0, m_bThread1); 19: 20: /////////////////////// 21: // MY CODE ENDS HERE 22: /////////////////////// 23: } In this function, the first thing that you do is to call UpdateData to keep the variables in sync with the controls on the window. Next, you retrieve a pointer to the document. Once you have a valid pointer, you call the document’s SuspendSpinner function, speci- fying the first thread and passing the current value of the variable tied to this check box to indicate whether the thread is to be started or stopped. To add this same functionality to the other thread check box, perform the same steps, adding the code in Listing 18.17. 464 Day 18 024 31240-9 CH18 4/27/00 12:59 PM Page 464 Doing Multiple Tasks at One Time––Multitasking 465 18 LISTING 18.17. THE CTaskingView OnCbthread2 FUNCTION. 1: void CTaskingView::OnCbthread2() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Sync the variables with the dialog 10: UpdateData(TRUE); 11: 12: // Get a pointer to the document 13: CTaskingDoc* pDocWnd = (CTaskingDoc*)GetDocument(); 14: // Did we get a valid pointer? 15: ASSERT_VALID(pDocWnd); 16: 17: // Suspend or start the spinner thread 18: pDocWnd->SuspendSpinner(1, m_bThread2); 19: 20: /////////////////////// 21: // MY CODE ENDS HERE 22: /////////////////////// 23: } Now that you’ve added the ability to start and stop the independent threads, compile and run your application. You’ll see that you can start and stop the independent threads with their check boxes, as well as the OnIdle tasks. At this point, if you play around with your application for a while, you’ll notice a bit of a difference between the two types of threads. If you have all threads running and are actively moving the mouse, you might notice the OnIdle spinners slowing down in their spinning (unless you have a very fast machine). The independent threads are taking a good deal of the processor time away from the main application thread, leaving less processor time to be idle. As a result, it’s easier to keep your application busy. The other thing that you might notice is that if you activate the menus or open the About window, although the OnIdle tasks come to a complete stop, the independent threads continue to run, as in Figure 18.9. These two threads are completely independent processes running within your application, so they are not affected by the rest of the application. 024 31240-9 CH18 4/27/00 12:59 PM Page 465 Shutting Down Cleanly You might think that you are done with this application until you try to close the applica- tion while one or both of the independent threads is running. You’ll see an unpleasant notification that you still have some work to do, as in Figure 18.10. It seems that leaving the threads running when you closed the application caused it to crash. 466 Day 18 FIGURE 18.9. The threads are not affected by the menu. FIGURE 18.10. Application error notification. Even though the application was closing, the threads were continuing to run. When these threads checked the value of the variable indicating whether to continue running or spin their spinners, they were trying to access a memory object that no longer existed. This problem causes one of the most basic and most fatal application memory errors, which you should eliminate before allowing anyone else to use the application. What you need to do to prevent this error is stop both of the threads before allowing the application to close. The logical place to take this action is the OnDestroy event message processing in the view class. This event message is sent to the view class to tell it to clean up anything that it needs to clean up before closing the application. You can add code to set both of the check box variables to FALSE so that the threads will stop them- selves and then call the SuspendSpinner function for each thread to make sure that both threads have stopped before allowing the application to close. You do not need to call UpdateData to sync the variables with the controls because the user doesn’t need to see when you’ve change the value of either check box. 024 31240-9 CH18 4/27/00 12:59 PM Page 466 Doing Multiple Tasks at One Time––Multitasking 467 18 To add this functionality to your application, add an event-handler function for the OnDestroy event message to the view class. This function does not normally exist in the view class that is created by the AppWizard, so you need to add it when it is needed in the descendent view class. Edit the function, adding the code in Listing 18.18. LISTING 18.18. THE CTaskingView OnDestroy FUNCTION. 1: void CTaskingView::OnDestroy() 2: { 3: CFormView::OnDestroy(); 4: 5: // TODO: Add your message handler code here 6: 7: /////////////////////// 8: // MY CODE STARTS HERE 9: /////////////////////// 10: 11: // Is the first thread running? 12: if (m_bThread1) 13: { 14: // Specify to stop the first thread 15: m_bThread1 = FALSE; 16: // Get a pointer to the document 17: CTaskingDoc* pDocWnd = (CTaskingDoc*)GetDocument(); 18: // Did we get a valid pointer? 19: ASSERT_VALID(pDocWnd); 20: 21: // Suspend the spinner thread 22: pDocWnd->SuspendSpinner(0, m_bThread1); 23: } 24: // Is the second thread running? 25: if (m_bThread2) 26: { 27: // Specify to stop the second thread 28: m_bThread2 = FALSE; 29: // Get a pointer to the document 30: CTaskingDoc* pDocWnd = (CTaskingDoc*)GetDocument(); 31: // Did we get a valid pointer? 32: ASSERT_VALID(pDocWnd); 33: 34: // Suspend the spinner thread 35: pDocWnd->SuspendSpinner(1, m_bThread2); 36: } 37: 38: /////////////////////// 39: // MY CODE ENDS HERE 40: /////////////////////// 41: } 024 31240-9 CH18 4/27/00 12:59 PM Page 467 In this function, you do exactly what you need to do. You check first one check box vari- able and then the other. If either is TRUE, you set the variable to FALSE, get a pointer to the document, and call the SuspendSpinner function for that thread. Now when you close your application while the independent threads are running, your application will close without crashing. Summary Today, you learned quite a bit. You learned about the different ways you can make your applications perform multiple tasks at one time. You also learned about some of the con- siderations to take into account when adding this capability to your applications. You learned how to make your application perform tasks when the application is sitting idle, along with some of the limitations and drawbacks associated with this approach. You also learned how to create independent threads in your application that will perform their tasks completely independently of the rest of the application. You implemented an appli- cation that uses both of these approaches so that you could experience how each approach works. 468 Day 18 When you start adding multitasking capabilities to your applications to per- form separate tasks, be aware that this is a very advanced aspect of pro- gramming for Windows. You need to understand a lot of factors and take into account far more than we can reasonably cover in a single day. If you want to build applications using this capability, get an advanced book on programming Windows applications with MFC or Visual C++. The book should include a substantial section devoted to multithreading with MFC and cover all the synchronization classes in much more detail than we did here. Remember that you need a book that focuses on MFC, not the Visual C++ development environment. (MFC is supported by most commercial C++ development tools for building Windows applications, including Borland and Symantec’s C++ compilers, so coverage for this topic extends beyond the Visual C++ environment.) Tip Q&A Q How can I use the other version of the AfxBeginThread to encapsulate a thread in a custom class? A First, the other version of AfxBeginThread is primarily for creating user-interface threads. The version that you used in today’s sample application is for creating what are called worker threads that immediately take off on a specific task. If you 024 31240-9 CH18 4/27/00 12:59 PM Page 468 Doing Multiple Tasks at One Time––Multitasking 469 18 want to create a user-interface thread, you need to inherit your custom class from the CWinThread class. Next, override several ancestor functions in your custom class. Once the class is ready to use, you use the RUNTIME_CLASS macro to get a pointer to the runtime class of your class and pass this pointer to the AfxBeginThread function, as follows: CWinThread* pMyThread = AfxBeginThread(RUNTIME_CLASS(CMyThreadClass)); Q Can I use SuspendThread and ResumeThread to start and stop my independent threads in my sample application? A Yes, but you need to make a few key changes to your application. First, in the OnNewDocument function, initialize the two thread pointers to NULL, as shown in Listing 18.19. LISTING 18.19. THE MODIFIED CTaskingDoc OnNewDocument FUNCTION. 1: BOOL CTaskingDoc::OnNewDocument() 2: { 3: if (!CDocument::OnNewDocument()) 4: return FALSE; 5: 6: // TODO: add reinitialization code here 7: // (SDI documents will reuse this document) 8: 9: /////////////////////// 10: // MY CODE STARTS HERE 11: /////////////////////// 12: 13: // Initialize the spinners 14: InitSpinners(); 15: 16: // Initialize the thread pointers 17: m_pSpinThread[0] = NULL; 18: m_pSpinThread[1] = NULL; 19: 20: /////////////////////// 21: // MY CODE ENDS HERE 22: /////////////////////// 23: 24: return TRUE; 25: } Next, modify the thread function so that the thread does not stop itself when the check box variable is FALSE but continues to loop, as shown in Listing 18.20. 024 31240-9 CH18 4/27/00 12:59 PM Page 469 LISTING 18.20. THE MODIFIED CTaskingDoc ThreadFunc FUNCTION. 1: UINT CTaskingDoc::ThreadFunc(LPVOID pParam) 2: { 3: // Convert the argument to a pointer to the 4: // spinner for this thread 5: CSpinner* lpSpin = (CSpinner*)pParam; 6: // Get a pointer to the continuation flag 7: BOOL* pbContinue = lpSpin->GetContinue(); 8: 9: // Loop while the continue flag is true 10: while (TRUE) 11: // Spin the spinner 12: lpSpin->Draw(); 13: return 0; 14: } Finally, modify the SuspendSpinner function so that if the thread pointer is valid, it calls the SuspendThread function on the thread pointer to stop the thread and the ResumeThread function to restart the thread, as shown in Listing 18.21. LISTING 18.21. THE MODIFIED CTaskingDoc SuspendSpinner FUNCTION. 1: void CTaskingDoc::SuspendSpinner(int nIndex, BOOL bSuspend) 2: { 3: // if suspending the thread 4: if (!bSuspend) 5: { 6: // Is the pointer for the thread valid? 7: if (m_pSpinThread[nIndex]) 8: { 9: // Suspend the thread 10: m_pSpinThread[nIndex]->SuspendThread(); 11: } 12: } 13: else // We are running the thread 14: { 15: // Is the pointer for the thread valid? 16: if (m_pSpinThread[nIndex]) 17: { 18: // Resume the thread 19: m_pSpinThread[nIndex]->ResumeThread(); 20: } 21: else 22: { 23: int iSpnr; 24: // Which spinner to use? 25: switch (nIndex) 470 Day 18 024 31240-9 CH18 4/27/00 12:59 PM Page 470 [...]... CModArt::NewDrawing() 2: { 3: int lNumLines; 4: int lCurLine; 5: 6: // Determine how many lines to create 7: lNumLines = rand() % m_iSegments; 8: // Are there any lines to create? 9: if (lNumLines > 0) 10: { 11: // Loop through the number of lines 12: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++) 13: { 14: // Create the new line 15: NewLine(); 16: } 17: } 18: } Finally, replace the maximum length of each... variable on line 20 in the NewLine function, as in Listing 19.5 LISTING 19.5 THE MODIFIED CModArt NewLine FUNCTION 1: void CModArt::NewLine() 2: { 3: int lNumLines; 18: 19: // Determine the number of parts to this squiggle 20: lNumLines = rand() % m_iLength; 21: // Are there any parts to this squiggle? 67 : } 025 31240-9 CH19 4/ 27/ 00 1:00 PM Page 481 Building Your Own Widgets—Creating ActiveX Controls...024 31240-9 CH18 4/ 27/ 00 12:59 PM Page 471 Doing Multiple Tasks at One Time––Multitasking 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: } 471 { case 0: iSpnr = 1; break; case 1: iSpnr = 3; break; } // Start the thread, passing a pointer to the spinner m_pSpinThread[nIndex] = AfxBeginThread(ThreadFunc, (LPVOID)&m_cSpin[iSpnr]); } } Workshop The Workshop provides... within a ActiveX container, such as a Visual C++ or Visual Basic application As you learned on Day 9, “Adding ActiveX Controls to Your Application,” ActiveX controls provide a series of interfaces used by the container application to trigger the various sets of functionality contained in the control Many of these interfaces are used for triggering events in the control or in the containing application Others... file, scroll to the top of the file, and add an include statement for the ModArt.h file, as in Listing 19 .6 LISTING 19 .6 THE CSquiggleCtrl INCLUDES 1: // SquiggleCtl.cpp : Implementation of the CSquiggleCtrl ActiveX Control class 2: 3: #include “stdafx.h” 4: #include “Squiggle.h” 5: #include “SquiggleCtl.h” 6: #include “SquigglePpg.h” 7: #include “ModArt.h” Adding Properties Because the two variables that... to maintain any drawings, unless the container application explicitly specifies that a drawing is to be maintained You’ll set these two variables accordingly in the control class constructor, as shown in Listing 19.8 LISTING 19.8 THE CSquiggleCtrl CONSTRUCTOR 1: CSquiggleCtrl::CSquiggleCtrl() 2: { 3: InitializeIIDs(&IID_DSquiggle, &IID_DSquiggleEvents); 4: 5: // TODO: Initialize your control’s instance... Finally, you’ll modify the two function that create the squiggle drawings so that they use these variables instead of the hard-coded values that they currently use To modify the NewDrawing function, replace the maximum number of squiggles in line 7 with the variable m_iSegments, as in Listing 19.4 LISTING 19.4 THE MODIFIED CModArt NewDrawing FUNCTION 1: void CModArt::NewDrawing() 2: { 3: int lNumLines;... Microsoft building networking capabilities into its operating systems, starting with Windows NT and Windows 95, these capabilities are becoming commonplace in all sorts of applications Some applications perform simple networking tasks such as checking with a Web site to see whether there are any updates to the program and giving the user the option of updating her copy of the program Some word processing applications... function on the control Once in the OnDraw function, you’ll determine whether you need to generate a new drawing or just draw the existing drawing Another thing to keep in mind is that you are responsible for 19 025 31240-9 CH19 4 86 4/ 27/ 00 1:00 PM Page 4 86 Day 19 drawing the entire area that the control occupies This means that you need to draw the background of the squiggle drawing, or else the squiggles... drawing code 5: //pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH) ➥GetStockObject(WHITE_BRUSH))); 6: //pdc->Ellipse(rcBounds); 7: // Do we need to generate a new drawing? 8: if (m_bGenNewDrawing) 9: { 10: // Set the drawing area for the new drawing 11: m_maDrawing.SetRect(rcBounds); 12: // Clear out the old drawing 13: m_maDrawing.ClearDrawing(); 14: // Generate the new drawing 15: m_maDrawing.NewDrawing(); . argument to a pointer to the 4: // spinner for this thread 5: CSpinner* lpSpin = (CSpinner*)pParam; 6: // Get a pointer to the continuation flag 7: BOOL* pbContinue = lpSpin->GetContinue(); 8:. squiggles in line 7 with the vari- able m_iSegments, as in Listing 19.4. LISTING 19.4. THE MODIFIED CModArt NewDrawing FUNCTION. 1: void CModArt::NewDrawing() 2: { 3: int lNumLines; 4: int lCurLine; 5: 6: . this thread 5: CSpinner* lpSpin = (CSpinner*)pParam; 6: // Get a pointer to the continuation flag 7: BOOL* pbContinue = lpSpin->GetContinue(); 8: 9: // Loop while the continue flag is true 10: