Now that you understand how the thread will be synchronized by encapsulating the synchronization logic within the data object, you will add the code necessary for the producer and consumer objects. As you'll recall, the delegates within each of these classes will be doing all the work. Before you implement the delegates, you need to create another constructor for each of the classes. Because this lesson is meant to show synchronization, each class will work with the same data object. This single IntDataBlock object will be created within the _tmain function and passed to the consumer and producer objects through each of their overloaded constructors. Create an overloaded constructor for each of the classes that accepts a pointer to an IntDataBlock object and assigns a private member variable to the constructor parameter. Also, change the _tmain function by creating an IntDataBlock instance and passing them to the producer and consumer classes rather than using the default constructors for each class. You can use Listing 19.3 to help you.
Delegates are used extensively throughout the .NET Framework. In order for a delegate to have access to data that it can use, you can follow the example this hour that places the delegate function within an actual object. By doing this, you gain the added benefit of object-oriented programming with delegates, which otherwise isn't possible by using a single global function instead. |
1: public __gc class Consumer 2: { 3: public: 4: Consumer(){} 5: 6: IntDataBlock* m_pDataBlock; 7: 8: Consumer( IntDataBlock* pDataBlock ) 9: { 10: m_pDataBlock = pDataBlock; 11: } 12: 13: void ThreadRun( ) 14: { 15: for(int i=1; i<=5; i++) 16: { 17: m_pDataBlock->WriteData( i ); 18: } 19: } 20: }; 21: 22: public __gc class Producer 23: { 24: public: 25: 26: IntDataBlock* m_pDataBlock; 27: 28: Producer( IntDataBlock* pDataBlock ) 29: { 30: m_pDataBlock = pDataBlock; 31: } 32: 33: void ThreadRun() 34: { 35: int nData; 36: for(int i=1; i<=5; i++) 37: { 38: nData = m_pDataBlock->ReadData( ); 39: } 40: } 41: }; 42: 43: // This is the entry point for this application 44: int _tmain(void) 45: { 46: int nResult = 0; 47: IntDataBlock* pData = new IntDataBlock( ); 48: 49: Producer* pProducer = new Producer( pData ); 50: 51: Consumer* pConsumer = new Consumer( pData ); 52: 53: Thread* pProdThread = new Thread 54: (new ThreadStart(pProducer, &Producer::ThreadRun )); 55: Thread* pconsThread = new Thread 56: (new ThreadStart(pConsumer, &Consumer::ThreadRun )); 57: 58: try 59: { 60: pconsThread->Start( ); 61: pProdThread->Start( ); 62: 63: pProdThread->Join( ); 64: pconsThread->Join( ); 65: } 66: catch (ThreadStateException* e) 67: { 68: Console::WriteLine(e->Message); 69: nResult = 1; 70: } 71: catch (ThreadInterruptedException* e) 72: { 73: Console::WriteLine(e->Message); 74: nResult = 1; 75: } 76: 77: Environment::ExitCode = nResult; 78: }
The code to actually produce and consume data is quite simple because all the data logic is contained within the data object. To produce data, simply create a for loop with a set number of iterations and for each iteration, call the WriteData function passing the loop index as the parameter. For the consumer object, simply create another loop and call the ReadData function on the IntDataBlock variable.
You can now compile your application. It may be a good idea to place Console::WriteLine calls throughout various places in your code. This will help you to see how the synchronization works and what the sequence of events is as data is produced and consumed. Your output should appear similar to Figure 19.4.
Top |