カクタスソフトウェア
カクタスソフトウェア
サウンド MIDI マルチメディア アプリケーション

サブスレッドによるポーリング手法(高精度バージョン)

このセクションではサブスレッドを用いて一定時間ごとにサービスを実施する手法を紹介します。

要点は以下のとおりです。

スレッドの起動

CreateThread()関数を用いて作業用スレッドを生成します。3番目のパラメータにスレッドの開始アドレス。4番目にユーザー変数。6番目にスレッド識別IDへのポインターを指定します。その他のパラメータは今回使用しません。スレッドの起動に成功するとハンドルが戻ります。

void EventThread::BeginThread(MyClass* pMyClass)
{
    for (int i=0; i<NUMHANDLE; i++)
    {
        HANDLE hdl = CreateEvent(NULL, FALSE, FALSE, NULL);
        m_hEventHandle[i] = hdl;
    }
    m_hThread = CreateThread(NULL, 0, EventFunc, pMyClass, 0, &m_dwThrdAddr);
}
スレッドを識別する変数に「ハンドル」と「ID」の2つがあります。ハンドルはスレッドの終了を待つ時に用い、IDはスレッドにメッセージを送る時に用います。利用する場面が異なりますので注意して下さい。

スレッド関数

スレッド関数はスタティックでなければなりません。スレッド内部は永久ループを構成し、スレッドを終了させるときはループを抜けるようにします。

スレッドへは引数を通してユーザー変数を伝えることができます。この引数は「LPVOID」であるため、元の変数(構造体やクラス)へのポインターにキャストして使います。

DWORD WINAPI EventFunc(LPVOID pContext)
{
MSG  msg;
BOOL bDone = FALSE;

    MyClass* pMyClass = (MyClass*)pContext;

    while (! bDone)
    {
        DWORD dwResult = MsgWaitForMultipleObjects(
            1, hEventHandle, FALSE, INFINITE, QS_ALLEVENTS);

        if (dwResult < (WAIT_OBJECT_0 + 1))
        {
            pMyClass->Notification();
            continue;
        }
        if (dwResult == (WAIT_OBJECT_0 + 1))
        {
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                switch (msg.message)
                {
                case WM_QUIT:
                    bDone = TRUE;
                    break;
                case その他のメッセージ:
                    // TODO
                    break;
                }
            }
        }
    }
    return 0;
}

作業スレッドの終了

PostThreadMessage()関数で、スレッドへ終了メッセージを送ります。

スレッドが終了したらイベントハンドルとスレッドハンドルをクローズします。

void EventThread::TermThread(void)
{
    StopTimer();

    PostThreadMessage(m_dwThrdAddr, WM_QUIT, 0, 0);
    WaitForSingleObject(m_hThread, 3000);

    for (int i=0; i<NUMHANDLE; i++)
    {
        CloseHandle(m_hEventHandle[i]);
    }
    CloseHandle(m_hThread);
}
作業用サブスレッドは録音・再生あらゆる場面で利用することができます。

イベントオブジェクトとシグナルの検出

イベントオブジェクトはマルチスレッド環境でタイミングの通知に用いられる仕組みです。イベントオブジェクトがシグナル状態になったとき、サブスレッド内のサービス関数が起動する仕組みを作ります。

まず、CreateEvent()関数でイベントオブジェクトを作成します。シグナルになったタイミングをつかまえるだけの単純な機能で良いので、初期値が非シグナルの自動リセットモードを指定しています。

自動リセットモードでは次のような動作を行います。MsgWaitForMultipleObjects()関数はイベントオブジェクトがシグナル状態になるまで待機します。ここでは「INFINITE」が指定されているのでいつまでも待ち続けます。イベントオブジェクトがシグナル状態になると、MsgWaitForMultipleObjects()関数から制御が戻り次のステップに進みます。と同時にイベントオブジェクトが非シグナル状態に復帰します。

以下の例では1つしか使用していませんが、m_hEventHandle配列には必要に応じて複数のイベントオブジェクトを設定することができます。

    HANDLE m_hEventHandle[1];
    m_hEventHandle[0] = CreateEvent(NULL, FALSE, FALSE, NULL);

次にサービス関数を起動する仕組みを作ります。MsgWaitForMultipleObjects()関数はどのイベントがシグナルになったのかその配列の場所を戻します。この例ではイベントは一つしか使っていないので必ずゼロが戻ります。戻り値を確認した後、Notification()関数を実行します。

「WAIT_OBJECT_0」はMsgWaitForMultipleObjects()関数の戻り値を判定するための定数です。戻り値が「WAIT_OBJECT_0+ハンドル数」より小さいとき、イベントオブジェクトがシグナルになったことを示します。

また戻り値が「WAIT_OBJECT_0+ハンドル数」に等しいときは、MsgWaitForMultipleObjects()関数がスレッドメッセージを受け取ったことを示します。

タイマーによるイベントオブジェクトの操作

次にマルチメディアタイマーを使ってイベントオブジェクトを定期的にシグナルにします。こうすることでサービス関数がタイマーで設定した時間間隔で定期的に呼び出されるようになります。

void EventThread::StartTimer(void)
{
    if (m_uTimerId != 0)    return;

    m_uTimerId = timeSetEvent(
        500,
        500,
        (LPTIMECALLBACK)m_hEventHandle[0],
        NULL,
        TIME_PERIODIC | TIME_CALLBACK_EVENT_PULSE);
}

TIME_CALLBACK_EVENT_PULSEフラグを用いてtimeSetEvent()関数を起動します。これによってm_hEventHandle[0]で設定したイベントオブジェクトが500mSec間隔でシグナルになります。

タイマーを停止するにはtimeKillEvent()コマンドを実行します。

void EventThread::StopTimer(void)
{
    if (m_uTimerId == 0)    return;
    timeKillEvent(m_uTimerId);
    m_uTimerId = 0;
}

演奏の開始・停止に合わせてタイマーも起動・停止させ、サービス関数の実行を制御します。

スレッドに永久ループを作り、Sleep()関数でタイミングを作る方法もあります。ただしこの方式は処理間隔にばらつきが生じる欠点があります。またSleep()実行中はスレッド終了のメッセージを処理できないため、終了処理に時間がかかる恐れがあります。

スレッドメッセージによる終了指示

プログラムを終了するときは、サブスレッドも終了させます。

PostThreadMessage()関数で、スレッドへ終了メッセージを送ります。引き続きWaitForSingleObject()関数でスレッドが終了するのを待ちます。ここでは安全のため最大3秒待っています。

正常にスレッドが終了すればほとんど時間がかかりませんが、何らかの事情でスレッドが終了しなかった場合、このステップでプログラムがブロックします。そのためここで「INFINITE」を指定してはいけません。

    PostThreadMessage(dwThrdID, WM_QUIT, 0, 0);
    WaitForSingleObject(hThread, 3000);

スレッドでメッセージを受ける手順は以下のとおりです。ここではPeekMessage()関数を用いています。一度取得したメッセージをキューから取り除くためにPM_REMOVEを指定します。

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        switch (msg.message)
        {
        case WM_QUIT:
            bDone = TRUE;
            break;
        }
    }
スレッドの終了判断については、スレッド側から親アプリケーションに問い合わせる仕組みにすることもできます。

ドキュメントの先頭へ

カクタスソフトウェア 技術協力 資料室 資料室の広場 SourceForge.jp お問い合わせ