6
votes

I have a large MFC based application that includes some potentially very slow tasks in the main thread. This can give the appearance that the application has hung when it is actually working its way through a long task. From a usability point of view, I'd like to be giving the user some more feedback on progress, and have an option to abort the task in a clean manner. While hiving the long tasks off into separate threads would be a better long term solution, I'm thinking a pragmatic short term solution is create a new GUI thread encapsulated in its own object complete with dialog including progress bar and cancel button, used in a similar manner to a CWait object. The main thread monitors the cancel status via an IsCancelled method, and finishes via a throw when required.

Is this a reasonable approach, and if so is there some MFC code out there already that I can use, or should I roll my own? First sketch looks like this

class CProgressThread : public CWinThread
{
public:
    CProgressThread(int ProgressMax);      
    ~CProgressThread()
    void SetProgress(int Progress);
    BOOL IsCancelled();
private:
   CProgressDialog  *theDialog;
}

void MySlowTask()
{
   CProgressThread PT(MaxProgress);
   try
   {
       {
           {  // deep in the depths of my slow task
              PT.SetProgress(Progress);
              if (PT.IsCancelled())
                 throw new CUserHasHadEnough; 
           }
        }
    }
    catch (CUserHasHadEnough *pUserHasHadEnough)
    {
        // Clean-up
    }
}    

As a rule, I tend to have one GUI thread and many worker threads, but this approach could possibly save me a bunch of refactoring and testing. Any serious potential pitfalls?

1
@Balog Pal, thanks for the link, but it doesn't really come up with anything conclusive and is also five years old at this stage. I use threads a lot with MFC with no issues, just that they're all currently worker threads.SmacL
working threads worked fine from the beginning. I never dared to mess with the single UI thread :). That topic covers the "new" MFC I'm not aware of significant changes since.Balog Pal
@ShaneMacLaughlin: The threading issues go deeper than MFC, it's a problem down at the lowest GDI level. It is safe if you keep the windows fully independent at GDI level. But once the dialog is a child, and it's routing messages to its parent, and the parent is on another thread ... bad things can happen.MSalters
@MSalters You are correct in the assessment, that the issue lies deeper than MFC. However, it's not GDI. It is the window manager. Plus, a dialog is usually not a child window, but rather an owned window. And it's not the message routing either, it's the fact that a window and its owner share the same input queue. It's not strictly unsafe to spread a GUI across different threads, if all participants are prepared. Getting it right is tricky at best, and certainly non-trivial. Good series, starts here.IInspectable

1 Answers

3
votes

Short answer, Yes, you can have multiple GUI thread in MFC. But you can't access the GUI component directly other than the created thread. The reason is because the Win32 under the MFC stores the GUI handler per thread based. It means the handler in one thread isn't visible to another thread. If you jump to the CWinThread class source code, you can find a handler map attribute there.

Windows (MFC) doesn't has hard difference between the worker thread & GUI thread. Any thread can be changed to GUI thread once they create the message queue, which is created after the first call related to the message, such as GetMessage().

In your above code, if the progress bar is created in one thread and MySlowWork() is called in another thread. You can only use the CProgressThread attributes without touch the Win32 GUI related functions, such as close, setText, SetProgress... since they all need the GUI handler. If you do call those function, the error will be can't find the specified window since that handler isn't in the thread handler mapping.

If you do need change the GUI, you need send the message to that progress bar owner thread. Let that thread handles the message by itself (message handler) through the PostThreadMessage, refer to MSDN for detail.