Multithreading & Doc-View - Urgent!

I'm writing a MDI application and I want to put each pair of Doc-View rinning in a separate thread.
The reason is that each view-doc receives really heavy stream of messages and is busy processing. The menu and other view-doc have to wait for the completion of the work this view-doc is doing. And I want each pair of view-doc doing almost the same heavy work simultaneously. I’m currently experimenting with CWinThread, but with no success yet.

Does anyone know how to do this?
Thanks in advance!

The view is used for displaying info (CEditView). A high priority thread gathers this info. This thread sends very intensively Windows messages to notify view for the incoming info, so while the view is displaying this info(, this really takes a lot of time), the app user interface is not available. Not to mention what happens when I create several doc-view instances, each receiving a lot of  messages from a separate working thread.
Who is Participating?
Here is what Paul DiLasci says about this in his MSJ Q&A C++ column (July 1998) ...

Q I have to write an application using the doc/view architecture of MFC so that every document and view instantiated on a File | New command is in a separate thread. How can I do this?

Many readers

A This is an example of the kind of multithreaded approach I would say is doomed from the outset. There are two main reasons. First, there’s the inherent problem that MFC’s internal handle maps—the tables that link handles (like HWNDs) to C++ objects (like CWnds)—are thread-specific. In other words, if a CWnd exists in one thread’s map, it doesn’t exist in any other. That’s why the MFC documentation advises you to use HWNDs instead of CWnd pointers when sharing Windows objects among threads. You can get by using CWnd, and there are clever ways to improve your odds, but I don’t want to even discuss them for fear I might encourage you.

Another reason a doc/view-per-thread approach is doomed is that there are simply too many pointers shared amongst docs, views, and frames. Each view points to its parent frame and doc, the doc has a list of view pointers as well as pointers to its CDocTemplate, and there are message maps and all sorts of other stuff inherited from CCmdTarget. In general, MFC has tons of spaghetti pointers pointing up the wazoo. It would be almost impossible to track down all these pointers, let alone synchronize their access using critical sections, which would be necessary in a multithreaded setting.

In general, whenever you consider using multiple threads, you should strive to limit your design in two basic ways. First, you want as few threads as possible that directly manipulate the UI. I would say only one: the main thread (process). No other thread should manipulate a window or dialog directly. Threads should perform work. If a thread needs to report its progress or update something on the screen, let it post an event or message to the main thread, which can in turn change the window text or draw a smiley face or whatever.

Alternatively, the main thread could query its threads about their state in its ON_COMMAND_UPDATE_UI handlers, and update the screen accordingly. One of the reasons I hate the bouncing balls sample program is that it fosters the idea of multiple threads painting a window. It’s cute for bouncing balls and screen savers, but it doesn’t work for most apps. You’re more likely to succeed, particularly in MFC, if you adopt the rule that only one thread manipulates the UI. (Some exceptions I can think of are CAD and page layout programs where painting the screen is so time-consuming you want to do it in an interruptible UI thread.)

My second stern rule for happy multithreading is: limit communication among threads. By that I mean limit the number of resources (objects) they share. In CThreadJob, m_bAbort is an example of a shared object because both the worker and main threads use it, potentially simultaneously. But the way m_bAbort is used (it’s either on or off) is so trivial, there’s no need to synchronize it, no need to worry about deadlock or any of the other multithreading nasties I absolutely guarantee will make you pull your hair out as soon as you start getting too clever. Of course, it’s not always possible to have such narrow bandwidth as in CThreadJob, but you should strive for it as much as possible.

So the main model for successful multithreading is a single UI thread with multiple worker threads and narrow communication. This may seem limiting, but it’s not. On the contrary, by limiting the design, you make life easier, not harder. I don’t know exactly what you have in mind for your MDI app, but you can achieve the effect of each doc/view running in a separate thread as follows: implement the entire UI in a single thread, and the entire app as a vanilla MFC MDI app. Then override your CDocument::OnOpenDocument (or perhaps Serialize) function to load the document in a separate thread. OnOpenDocument would set a state indicator to LOADING, launch a thread to load the data, and return immediately. The view would recognize the document’s LOADING state and display accordingly. Perhaps it would paint a message like "Still loading..." or draw an empty rectangle where a picture is expected, as Web browsers do. Your doc/view’s ON_COMMAND_UPDATE_UI handlers would likely disable many commands if the document weren’t loaded yet.

When the worker thread finally finishes loading the file, it would post a message to the doc. When it got the message, the doc would set its state to LOADED and call UpdateAllViews, perhaps with a hint parameter to tell the view exactly what’s going on. You could post the message as a WM_NOTIFY message to the main window, with the WM_NOTIFY handler implemented in your document class. You could even post partial progress events as the document is loaded. The view would have to know how to display partial results the way most Web browsers know how to display partial GIF and JPG files. In fact, most Web browsers work exactly as I am describing, as do URL monikers and CAsyncMonikerFile, which have callback interfaces to report OnProgress and OnDataAvailable. If your app has other long operations such as lengthy calculations, you can implement worker threads for those too.

If you take this approach, you’ll end up with an app that appears to run each MDI child in a separate thread, when in fact there is one main UI thread communicating in a very limited manner with many worker threads. Happy multithreading!

jsarachinovAuthor Commented:
Adjusted points from 50 to 75
It would help to know a little more about what your application does and what you're trying to accomplish.  In particular, what kinds of messages are the doc-views receiving? (Windows messages, something else?) also where are the messages coming from?  What is the intent of the application?

My gut feeling is that you are probably trying to use threads in a way that they are not well-suited for, but I can't tell for sure without more info.

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Hi jsarachinov,

I'm not sure if it's somehow possible to 'put' a doc/view pair into a thread ... I even don't think it's a good strategy for you'll have lots of work doing sync. Why not simply create a thread for each doc/view which handles the heavy work and sends messages to the view/doc whenever needed?

The MFC's structure does not easily lend itself to this usage of threads.

Without all the details, my feeling is that the division of labour within your application needs to be rethought.

Usual choice: Use one thread for the front-end and spin threads as necessary to handle work underneath.

jsarachinovAuthor Commented:
Edited text of question.
jsarachinovAuthor Commented:
Edited text of question.
Okay, the new info helps.  I'm going to make a couple of assumptions:
* The working thread runs periodically
* Some (minimal) amount of latency between the time data is collected and the time a message is displayed is acceptable

also, if I understand you correctly, the worker thread is sending the data to the View using some type of SendMessage call, either explicitly, or through one of the CEdit control member funnctions (which translate into a  SendMessage).  Since SendMessage is synchronous this ties the worker thread and the UI thread together.

Given that, you might want to consider the following solution:

Provide an offline (at least from the UI thread's point of view) way for the worker thread to pass the data through.

One way to do this would be to add the following members to your view class:

CStringArray      m_NewData;
CCriticalSection  m_NewDataCS;

As the worker thread collects data, it acquires a lock on the critical section, adds data to the end of the array, and then releases the critical section.  
The view class could either periodically check m_NewData for new data, or the worker thread could post a message to the view to let it know that new data has been added.  Since PostMessage is used, the UI will process the message in it's normal course of events.  Since the worker thread is running at high-priority, the UI thread (presumably running at normal priority) won't run until the worker thread has blocked.

This would look something like this:

struct ThreadParms {
  CStringList  * pData;
  CCriticalSection lock;
  HWND  hWndNotify;
  UINT  nMsgNotify;

ThreadFunc( LPVOID parms )
  ThreadParms * pParm= (ThreadParms*)parms;

  while (<some condition is true>) {
  <Block waiting for data to arrive>
  while (<more data is available>) {
    pParm->pData->AddTail( <new data> );
  ::PostMessage( hWndNotify, nMsgNotify, 0, 0 );

class CMyView {
  ThreadParms  m_Parms;

  m_Parms.pData= new CStringList;
  // etc.
  _beginthreadex( .... );

CMyView::OnNewData( )
  // Acquire the lock, grab the list and replace it, minmizing the time we hold the lock
  CStringList * pData= m_Parms.pData;
  m_Parms.pData= new CStringList;

  // Now add pData to the view...

Does this help?

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.