locked
MFC GUI message is blocked in multithread environment RRS feed

  • Question

  • Hi all,

    First thank you for reading my problem. I have developped a MFC program on Wince4.2, there are five threads in the program, a GUI thread and 4 other worker threads, and there is no synchronization among them. In the GUI thread I set a global timer(1000ms) to refresh the button and editbox for updating their titles. At first, I assign each thread 100ms quantum on default in WinCE, and the GUI thread is always blocked, when I trace it I find the global timer works very well but the WM_PAINT message is not handled. The global timer can be processed means that the GUI thread is scheduled successfully but the WM_PAINT message generated from the global timer is not handled by the thread.

    Then I adjust the quantum for each thread so that the GUI thread can get more time slices for handling GUI messages. It does have some effects and the GUI responds more quickly than before, but the problem of blocking WM_PAINT message still exists while the  probability is low now.

    I have no idea how to proceed, thank you for teaching me!
    Wednesday, June 10, 2009 3:13 AM

Answers

  • About the timer - WM_TIMER and WM_PAINT are the last messages handled by the window. So if you window receives a lot of WM_TIMER, you will never see WM_PAINT. If your drawing procedure is too long, you will be there all the time and the window message query will be full of other messages.
    Does not matter how many WM_PAINT are in the query - one handled WM_PAINT will validate the whole window. But...
    You have to be sure that when you draw a control ( a window), there is no other calls that may invalidate your window again (I think you have such kind of mistake).

    I may only recommend you something that I successfully use for a long time.
    If you have many controls on a dialog, you don't need to worry about the drawing if:
    1. the drawing procedure in each control and in the dialog are very fast. For example, you may have an internal DC in your controls and keep the last drawn image of the control there. So if your control does not change own state, you need just copy this internal DC on the paint DC.  That's right, if we are talking about your own controls (designed by you).
    2. Do not load any image when you draw a control. Load everything just once in OnInitDialog. More then it, you can create internal DCs for you images and select your the images' HBITMAP's there, so, when you draw the control you just call BitBlt from one DC to another. Anyway, do not load any image in the drawing procedure - any load function will look for your file on the disk and it will take time.
    3. Only invalid controls are drawn on your dialog when you call WindowUpdate (WindowUpdate is the same as SendMessage(WM_PAINT)). So if you know that now only one CStatic was updated, before WindowUpdatel, you may call pStatic->Invalidate() and then WindowUpdate() for the dialog. In this way, even your another control changed its state and was redrawn, you will not see this change on the screen.
    4. You mau complicate a bit your dialog and override OnPaint. In your OnPaint you may handle a special mode - for example, to have an internal variable m_bFast and if it is TRUE, draw only specific control.
    5. You may override WM_ERASEBKGRND (erase background) message - just put return TRUE there and do all you need in the OnPaint.
    But the main rule is to invalidate only the regions that should be redrawn now.
    Please check WindowUpdate, if it is not pure WinAPI ::WindowUpdate(hWnd,...) - before WindowUpdate some one could put Invalidate for the whole dialog, so all controls will be redrawn.
    It will be great if it will help you.

    • Marked as answer by kikilovefish Friday, June 12, 2009 9:59 AM
    Thursday, June 11, 2009 3:06 PM

All replies

  • In addition, this program is modal dialog based. I read the stuff of MFC and it says the dialog has a different mechanism from frame window when processing messages, but I don't think it is the key point. There are many dialogs in my program, when a dialog pops up, I just put it as the topmost one, the other dialogs is hiden by the topmost one. 
    Wednesday, June 10, 2009 3:39 AM
  • WM_PAINT is handled last.
    But I think, it will help if in yout timer handler (OnTimer()?), you will add WindowUpdate call. Maybe just Invalidate(FALSE) will help. Please try.
    I think it is more safety to add a synchronization, ceritical sections to your app.
    Wednesday, June 10, 2009 7:39 AM
  • Hi Pavel,

    Thanks very much.

    I also think of the idea you mentioned, I will try it hours later. The timer handler is OnTimer,  there are about 20 controls to be refreshed in the timer which means it will generate about 20 WM_PAINT messages in the main thread queue, if I refresh the control with updatewindow and bypass the message queue,  it may save some slices for other message.  Do you know how fast the thread processes a normal message in average? I google it and with no answer.

    There is few synchonization in my program, I use critical section, at first I doubt about it, then I find that even in the extreme case the critical section only take 3ms to process.

    Now other worker threads works smoothly with the external devices. The only problem is the GUI.

    Thanks again.

    Best Regards
    Wednesday, June 10, 2009 8:23 AM
  •  Do you know how fast the thread processes a normal message in average? I google it and with no answer.
    The answer is "it varries."  Not taking into account different speeds and computational capabilities of different processors the amount of time required to process a message is really going to depend on what you do to respond to the message and how much CPU bandwidth is available to the thread. So there's not going to be a standard answer for this.
    Joel Ivory Johnson
    Wednesday, June 10, 2009 1:20 PM
  • Hi Pavel,

    Yesterday I got a weired problem, in dialog DA I click a button B, the procedure of B will pop a dialog DB, DB has lots of controls and a timer,  sometimes after I click button B, DB does not pop(the GUI has no response and still in DA), but when I use "wprintf" to trace the program I find DB::InitDialog has been executed and the timer of DB works well, but it does not paint itself.

    At first I think maybe the timer take too much long time, I define it(OnTimer) as void(no code in it), the problem still exists, then I do not SetTimer, the problem does not exist. I think it means the WM_TIMER message blocks the WM_PAINT, but how could that happen, in OnTimer I do nothing, the thread should have opportunity to process WM_PAINT.
    Thursday, June 11, 2009 1:15 AM
  • About the timer - WM_TIMER and WM_PAINT are the last messages handled by the window. So if you window receives a lot of WM_TIMER, you will never see WM_PAINT. If your drawing procedure is too long, you will be there all the time and the window message query will be full of other messages.
    Does not matter how many WM_PAINT are in the query - one handled WM_PAINT will validate the whole window. But...
    You have to be sure that when you draw a control ( a window), there is no other calls that may invalidate your window again (I think you have such kind of mistake).

    I may only recommend you something that I successfully use for a long time.
    If you have many controls on a dialog, you don't need to worry about the drawing if:
    1. the drawing procedure in each control and in the dialog are very fast. For example, you may have an internal DC in your controls and keep the last drawn image of the control there. So if your control does not change own state, you need just copy this internal DC on the paint DC.  That's right, if we are talking about your own controls (designed by you).
    2. Do not load any image when you draw a control. Load everything just once in OnInitDialog. More then it, you can create internal DCs for you images and select your the images' HBITMAP's there, so, when you draw the control you just call BitBlt from one DC to another. Anyway, do not load any image in the drawing procedure - any load function will look for your file on the disk and it will take time.
    3. Only invalid controls are drawn on your dialog when you call WindowUpdate (WindowUpdate is the same as SendMessage(WM_PAINT)). So if you know that now only one CStatic was updated, before WindowUpdatel, you may call pStatic->Invalidate() and then WindowUpdate() for the dialog. In this way, even your another control changed its state and was redrawn, you will not see this change on the screen.
    4. You mau complicate a bit your dialog and override OnPaint. In your OnPaint you may handle a special mode - for example, to have an internal variable m_bFast and if it is TRUE, draw only specific control.
    5. You may override WM_ERASEBKGRND (erase background) message - just put return TRUE there and do all you need in the OnPaint.
    But the main rule is to invalidate only the regions that should be redrawn now.
    Please check WindowUpdate, if it is not pure WinAPI ::WindowUpdate(hWnd,...) - before WindowUpdate some one could put Invalidate for the whole dialog, so all controls will be redrawn.
    It will be great if it will help you.

    • Marked as answer by kikilovefish Friday, June 12, 2009 9:59 AM
    Thursday, June 11, 2009 3:06 PM
  • Hi Pavel,

    You are so kind, thank you again.
    Yesterday I trace my dialog with remote spy++, at last we find the reason. There are two timers at any time in my program, for example, one in dialog DA, the other one in dialog DB, when I click a button to pop DB, sometimes, in a prop time, the timer of DB and DA will block the WM_PAINT message.

    I also use PeekMessage to scan the message queue,  if there is only one WM_TIMER in the queue just after the InitDialog process, the dialog DB would be painted, but if two the probability of blocking is  much greater.  It means that the WM_PAINT message is blocked by two WM_TIMER messages, it cannot get chance to be processed. So in OnTimer of DB, I peek the WM_TIMER message and dispatch it, it can promise that  there will be only one WM_TIMER in the queue at most time. So the WM_PAINT message can be generated and processed.

    I think the root reason is that first there are much controls in dialog DB so the initialization process is long, second there are five threads in my program so GUI thread cannot get enough time to process messages, third as you say the control is implemented by ourselves and the paint process is not efficient. 

    Your explanation is very impressive. It helps.

    May you have a happy day.

    Best Wishes 
    Friday, June 12, 2009 9:59 AM