Завершение рабочего потока из родительского потока - MFC

Я только начал изучать MFC и пишу одно диалоговое приложение для лучшего понимания многопоточности.

В основном диалоговом окне есть индикатор выполнения, кнопка «Пуск» и кнопка «Отмена».

При нажатии кнопки «Пуск» я создаю рабочий поток для выполнения некоторой обработки (через вызов 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 () (хотя это не было предложено), но мне не удалось убить поток.

Прокомментируйте и поделитесь своими мыслями о завершении рабочего потока из родительского потока.

Я использую Visual Studio 2010 в Windows 7

TIA


person MajorBetaVictory    schedule 14.01.2014    source источник
comment
Просто краткое указание, никогда никогда не используйте TerminateThread.   -  person Roger Rowland    schedule 14.01.2014
comment
@Roger Да (плохая идея использовать) !!!. Я просто попробовал проверить, может ли он решить проблему ..   -  person MajorBetaVictory    schedule 14.01.2014
comment
Кроме того, не нужно делать delete pObjectHandle, вы, вероятно, разобьетесь. Также вы преобразуете свой lParam в два разных типа, что не сработает. Правильный способ связи между потоками - это события. На SO есть много примеров. Боюсь, с вашим текущим кодом много проблем :-(   -  person Roger Rowland    schedule 14.01.2014
comment
Я пробовал использовать WaitForSingleObject, но это не сработало. Он пытается и успешно выполняет первый вызов API, а затем пытается его прервать.   -  person MajorBetaVictory    schedule 14.01.2014
comment
Я обновил свой ответ несколькими примерами, основанными на вашем текущем коде - есть много способов сделать это, но, возможно, это будет полезным началом.   -  person Roger Rowland    schedule 14.01.2014


Ответы (1)


Я бы изменил ваш код примерно так.

Имейте некоторые переменные-члены в вашем диалоговом классе для хранения дескриптора потока и дескриптора события (инициализируйте значение 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;
}

Я пропустил инициализацию, очистку указателей, дескрипторов и т. Д., Но, надеюсь, вы поняли общую идею.

Существует несколько способов кодирования цикла потока, вы можете сделать это, как указано выше, когда вы периодически проверяете, сигнализируется ли событие, или вы можете дождаться события, чтобы получить сигнал о выполнении работы. Оба являются общими шаблонами и часто используются вместе с двумя событиями - одним для запуска работы, а другим - для уничтожения. См. этот ответ для некоторых важных моментов, на которые следует обратить внимание при ожидании нескольких событий.

Для простого обновления индикатора выполнения вы можете поместить проверку событий внутри рабочего цикла, примерно так:

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;
}
person Roger Rowland    schedule 14.01.2014