使用临时消息派发

作者:陆麟
转载请征得作者同意.
2002.1.14



忙私,事很久没有更新主页,今天先更新一下, 免得大家老是白来Inside Programming.

有时候需要一些比较古怪的代码, 来达到一些很普通的效果. 例如, 在自己的消息处理函数中要创建一个新的线程, 而新的线程必须创建新的窗口, 并且等窗口完全出现后, 原来的消息处理函数将进行一项长时间的计算活动. 在MFC的代码中通过普通的方案无法实现这样的要求.
例如, 通常的作法是

void CLASSX::OnOK( )
{
    //
    //创建线程, 并跑起来.
    //
    CSOMETHREAD *Step = (CSOMETHREAD *)AfxBeginThread(RUNTIME_CLASS(CSOMETHREAD));
    while (Step->InternalFlag != READY)
    {
        //等到线程把InternalFlag 设置为 READY
        Sleep(1000);
    }
    //开始长时间计算
    Compute(X);
    //计算结束, 线程也要结束
    Step->Stop();
}

如果CSOMETHREAD是个WORKER线程, 没有问题, 但是如果CSOMETHREAD将有USER INTERFACE. 那可糟糕. 什么东西都不会被显示出来. 而且while可能会进入死循环. 因为如果InternalFlag是在窗口创建完成后设立的, 那就根本是个DeadLock. OnOk等待InternalFlag为READY, 而线程因为创建窗口时, MFC本身的MESSAGE LOOP没有返回, 根本不能处理新的窗口的消息. 导致新窗口的WM_CREATE等一系列消息无法处理, 窗口创建不能完成... 尽管新线程本身的Run函数处理中使用了标准的
MSG msg;
while (GetMessage(&msg,0,0,0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
UI消息派发, 但是因为创建窗口没有完成, 没有办法执行到Run函数处, 这个消息循环还用不到.

OK, Inside Programming现在给出一种替代方案. 无效重新设计程序结构, 使用临时消息处理器就能完美解决此问题.
我们要干的是在while循环中使用自己的消息循环. 看下面的代码:
void CLASSX::OnOK( )
{
    //
    //创建线程, 并跑起来.
    //
    CSOMETHREAD *Step = (CSOMETHREAD *)AfxBeginThread(RUNTIME_CLASS(CSOMETHREAD));
    while (Step->InternalFlag != READY)
    {
        //等到线程把InternalFlag 设置为 READY
        MSG msg;
        if (GetMessage(&msg,0,0,0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    //开始长时间计算
    Compute(X);
    //计算结束, 线程也要结束
    Step->Stop();
}

不使用Sleep()函数, 用自己的临时的消息派发, 就能完美解决问题. 当InternalFlag被设置时, 意味着新线程的Run已经被执行到. 新线程自己的消息派发已经开始工作. 这样, 死循环就消除了.:) 此示例代码极度适用于MFC. 对很多架构不完善导致的UI问题可以解决.