4

MFC の学習を始めたばかりで、マルチスレッドの理解を深めるためにダイアログ ベースのアプリケーションを 1 つ作成しています。

メイン ダイアログには、プログレス バー、[開始] ボタン、および [キャンセル] ボタンがあります。

開始ボタンをクリックすると、(API 呼び出しを介して) 何らかの処理を行うワーカー スレッドが作成され、メイン スレッドがプログレス バーを処理します。

プログレスバーのステータスを更新および停止するために、いくつかのWindowsメッセージを定義しました

WM_UPDATE_CONTROL
WM_STOP_CONTROL

以下は、これまでに作成したコードです

HWND* phObjectHandle;
CWinThread* thread;

void CprogCtrlDlg::OnBnClickedStart() {
    phObjectHandle = new HWND;    // Set object handle for Worker thread
    *phObjectHandle = GetSafeHwnd();

    // create worker thread
    if(NULL == (thread = AfxBeginThread(ThreadFunc, phObjectHandle))) {
        EndDialog(IDCANCEL);
    }

    AfxMessageBox(L"Thread started");
    // Set Progress bar to marquee
}

void CprogCtrlDlg::OnBnClickedCancel() {
    // kill the Worker thread
}

UINT CprogCtrlDlg::ThreadFunc(LPVOID pParam) { 
    HWND *pObjectHandle = static_cast<HWND *>(pParam);
    CprogCtrlImpDlg* threadDlg = (CprogCtrlImpDlg*) pParam;

    return threadDlg->ThreadFuncRun(pObjectHandle);
}

UINT CprogCtrlDlg::ThreadFuncRun(HWND* pObjectHandle) {

    ::PostMessage(*pObjectHandle, WM_UPDATE_CONTROL, 0, 0);

    // repetitive API CALL in a loop

    ::PostMessage(*pObjectHandle, WM_STOP_CONTROL, 0, 0);
    AfxMessageBox(L"Thread completed");

    return 0;
}

[キャンセル] ボタンがクリックされた場合、親スレッドからワーカー スレッドを終了させたい。

TerminateThread() を使用してみましたが(推奨されたものではありませんでしたが)、スレッドを強制終了できませんでした。

親スレッドからワーカー スレッドを終了することについてコメントし、あなたの考えを共有してください。

Windows 7 で Visual Studio 2010 を使用しています。

ティア

4

1 に答える 1

6

私はあなたのコードをこのように修正します。

スレッド ハンドルとイベント ハンドルを保持するために、ダイアログ クラスにいくつかのメンバー変数を用意します (コンストラクターで NULL に初期化します)。

CWinThread* m_hThread;
HANDLE m_hKillEvent;

スレッド エントリ ポイントとして静的関数を使用し、ダイアログthisをパラメーターとして渡し、呼び出しをクラス インスタンスにデリゲートして、ダイアログのすべての変数にアクセスできるようにします。

UINT ThreadFunc(LPVOID pParam) 
{ 
    // static thread func - delegate to instance
    CprogCtrlDlg* pDlg = static_cast<CprogCtrlDlg*>(pParam);
    return pDlg->ThreadFuncRun();
}

スレッドを開始するときに、イベントも作成します。

void CprogCtrlDlg::OnBnClickedStart() 
{
    // create worker thread
    m_hKillEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hThread = AfxBeginThread(ThreadFunc, this);
    AfxMessageBox(L"Thread started");
}

スレッドを強制終了するには、イベントを設定してスレッド ハンドルを待機するだけです。

void CprogCtrlDlg::OnBnClickedCancel() 
{
    // kill the Worker thread
    SetEvent(m_hKillEvent);

    // wait for it to die
    DWORD dwRet = WaitForSingleObject(m_hThread->m_hThread, 5000);
    if (dwRet == WAIT_TIMEOUT)
    {
        // thread failed to die after 5 seconds
        // error handling (maybe TerminateThread here)
    }
}

スレッド関数 (現在はダイアログ クラスにあります) では、自分自身にメッセージを投稿して進行状況を示し、イベントの待機を使用してキル リクエストをキャッチできます。

UINT CprogCtrlDlg::ThreadFuncRun() 
{
    // instance thread func
    PostMessage(WM_UPDATE_CONTROL, 0, 0);

    // main loop
    while (true)
    {
        // check kill
        DWORD dwRet = WaitForSingleObject(m_hKillEvent, 0);
        if (dwRet == WAIT_OBJECT_0) break;

        // do a little work here and update progress
        // ... so this is part of your working loop ...
        PostMessage(WM_UPDATE_CONTROL, 0, 1 /*2,3,4,...*/);
    }

    // normal thread exit
    PostMessage(WM_STOP_CONTROL, 0, 0);
    return 0;
}

初期化、ポインターのクリーンアップ、ハンドルなどは省略しましたが、私が望む一般的なアイデアは得られます。

スレッド ループをコーディングする方法はいくつかあります。上記のように、イベントが通知されているかどうかを定期的にチェックするか、イベントが通知されて作業を行うのを待つことができます。どちらも一般的なパターンであり、多くの場合、2 つのイベント (1 つは作業のトリガー用、もう 1 つは強制終了用) と共に使用されます。複数のイベントを待機する場合に注意すべき重要な点については、この回答を参照してください。

簡単なプログレス バーの更新の場合、次のようにイベント チェックをワーク ループ内に配置できます。

UINT CprogCtrlDlg::ThreadFuncRun() 
{
    // instance thread func
    PostMessage(WM_UPDATE_CONTROL, 0, 0);

    // main loop
    for (int i = 0; i < 100; ++i)
    {
        // check kill
        DWORD dwRet = WaitForSingleObject(m_hKillEvent, 0);
        if (dwRet == WAIT_OBJECT_0) break;

        // do a little work here and update progress
        PostMessage(WM_UPDATE_CONTROL, 0, (LPARAM)i);
    }

    // normal thread exit
    PostMessage(WM_STOP_CONTROL, 0, 0);
    return 0;
}
于 2014-01-14T15:19:40.333 に答える