Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Как закрыть thread, созданный в DLL-ке?  (Прочитано 14003 раз)
0 Пользователей и 3 Гостей смотрят эту тему.
jur
Помогающий

lt
Offline Offline

« : 18-03-2009 10:02 » 

Привет!

Что-то странное происходит...

Работа ведется в MS VS 2008 на Visual C++. Сделал тестовое приложение (на MFC), к которому пристегнута DLL-ка. DLL-ка написана на чистом API. Она создает thread при старте и должна закрыть его при выгрузке. Мне бы не хотелось вручную закрывать поток из главного приложения (к примеру, в OnDestroy()), т.к. в разных DLL-ках могут появляться разные потоки. Некузяво как-то всех их отслеживать в главном приложении... Вот код запуска/выгрузки DLL-ки:

Код:
HANDLE   hShutdownEvent = NULL;

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
  switch (ul_reason_for_call)
  {
    case DLL_PROCESS_ATTACH:
      hShutdownEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
      pThread = new CThread;
      pThread->Start();
      break;
    case DLL_PROCESS_DETACH:
      SetEvent(hShutdownEvent);
      Sleep(10);
      pThread->Stop();
      SAFE_DELETE(pThread);
      SAFE_CLOSE_HANDLE(hShutdownEvent);
      break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
      break;
  }
  return TRUE;
}

Для закрытия thread-а используется hShutdownEvent, а функция потока этот ивент ожидает. Когда ивент взводится, поток завершается. Функция потока проста, как дверь:

Код:
DWORD WINAPI Thread_func(LPVOID param)
{
  CThread* p = (CThread*)param; // Pointer to Thread class
  HANDLE   hEventArray[2];      // First event must be close event
  DWORD    nEvent;

  ::OutputDebugString("Thread_func(): Start thread function\n");
  hEventArray[0] = p->_h_shutdown_event;  // highest priority shutdown event
  hEventArray[1] = hShutdownEvent;
  while(1) {
    nEvent = WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE);
    if(nEvent == 0) { // Shutdown event
      ::OutputDebugString("Thread_func(): Shutdown event 1\n");
      break;
    }
    else if(nEvent == 1) { // Second shutdown event
      ::OutputDebugString("Thread_func(): Shutdown event 2\n");
      break;
    }
    else {
      // Unknown event
    } // end if
  } // end while(1)
  // Leave thread
  ::OutputDebugString("Thread_func(): Leave thread function\n");
  SAFE_CLOSE_HANDLE(p->_h_thread);
  return 666;
}

(Здесь _h_shutdown_event - это тоже ивент закрытия потока, только он создается внутри класса. Сначала я только его использовал, потом решил попробовать еще и внешний ивент. Убирать _h_shutdown_event пробовал - не помогает.)

А засада в том, что если поток закрывать из основной программы, то все прекрасно (я кнопочку завел для рестарта потока). Но если поток пытается закрыться перед выгрузкой DLL-ки, то это не работает. Хендл ивента нормальный, SetEvent(hShutdownEvent) отрабатывает без ошибки, а функция потока этого события не видит и, соответственно, из цикла не выходит. Вот отладочный лог:

Код:
...
'Thread_probe_main.exe': Loaded 'C:\WINDOWS\system32\imm32.dll'
'Thread_probe_main.exe': Loaded 'C:\WINDOWS\system32\winmm.dll'
'Thread_probe_main.exe': Loaded 'C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.5512_x-ww_35d4ce83\comctl32.dll'
Thread_func(): Start thread function   <=== Поток запустился при старте DLL-ки
'Thread_probe_main.exe': Loaded 'C:\WINDOWS\system32\uxtheme.dll'
'Thread_probe_main.exe': Loaded 'C:\Program Files\Dexpot\hooxpot.dll'
'Thread_probe_main.exe': Loaded 'C:\WINDOWS\system32\MSCTF.dll'
Thread_func(): Shutdown event 1        <=== Поток закрылся при нажатии в приложении кнопки перезапуска и ...
Thread_func(): Leave thread function
The thread 'Win32 Thread' (0xa48) has exited with code 666 (0x29a).
Thread_func(): Start thread function   <=== ... снова запустился
Thread_func(): Shutdown event 1
Thread_func(): Leave thread function
The thread 'Win32 Thread' (0xbd4) has exited with code 666 (0x29a).
Thread_func(): Start thread function
Thread_func(): Shutdown event 1
Thread_func(): Leave thread function
The thread 'Win32 Thread' (0xe2c) has exited with code 666 (0x29a).
Thread_func(): Start thread function
The thread 'Win32 Thread' (0x62c) has exited with code 0 (0x0). <=== А вот тут я закрыл приложение, SetEvent сработал, но поток этого не увидел
The program '[2208] Thread_probe_main.exe: Native' has exited with code 0 (0x0).

Что за странности? Ведь DLL-ка сама создала ивент, сама его взводит, а её родной поток собственный кровный ивент видеть перестает... Такое чувство, что закрытие основного приложения каким-то образом "выключает" механизм обработки событий...

Подскажите, пожалуйста, как это побороть? Уж часом не SECURITY_ATTRIBUTES-ли тут воду мутят?... А я с ними совсем "не дружу" и что делать - без понятия...

P.S. Не знаю, важно ли это: я создаю поток функцией CreateThread. Вот так:

Код:
  // Create new thread
  _h_thread = CreateThread(NULL,0,Thread_func,this,0,&_dw_thread_id);

P.P.S. Заметил еще одну странность. Когда приложение работает, то VS в отладочном окне Threads показывает два потока (останов в функции потока):

Код:
0	 	2608	Main Thread	Main Thread	CWnd::RunModalLoop	Normal  0
0 > 2256 Worker Thread Thread_func Thread_func         Normal  0

А когда я закрываю приложение, то остается только основной поток (останов в точке DLL_PROCESS_DETACH):

Код:
0	 	2608	Main Thread	Main Thread	CWnd::RunModalLoop	Normal  0

Кто и почему закрыл мой Worker Thread?! Видимо именно здесь собака порылась, да?!

« Последнее редактирование: 18-03-2009 10:05 от jur » Записан

MPEG-4 - в массы!
jur
Помогающий

lt
Offline Offline

« Ответ #1 : 18-03-2009 10:24 » new

Попробовал _beginthreadex - не помогает...

Записан

MPEG-4 - в массы!
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 18-03-2009 10:25 » 

Цитата
SAFE_CLOSE_HANDLE(p->_h_thread);
- а вот это точно можно делать прямо в функции потока? Что-то нет уверенности.
Кстати, ожидание завершения потока есть? Или подаём сигнал завершаться и уходим закрывать эвент?
Записан
jur
Помогающий

lt
Offline Offline

« Ответ #3 : 18-03-2009 11:20 » 

Цитата
SAFE_CLOSE_HANDLE(p->_h_thread);
- а вот это точно можно делать прямо в функции потока? Что-то нет уверенности.

Можно. Ведь этот хэндл больше использоваться не будет, т.к. функция потока закрывается.

Кстати, ожидание завершения потока есть? Или подаём сигнал завершаться и уходим закрывать эвент?

Признаком завершения потока служит закрытие его хэндла. Вот как это делается в классе:

Код:
void CThread::close_thread()
{
  int timeout = 0;

  if(_h_thread) {
    SetEvent(_h_shutdown_event);
    while(_h_thread != NULL) {    // Хэндл объявлен так: volatile HANDLE _h_thread;
      Sleep(5);
      timeout++;
      if(timeout > 200) { // Wait 1 sec
        MessageBox(NULL,"  Cannot close thread  ","Start()",MB_OK|MB_APPLMODAL|MB_TOPMOST);
        SAFE_CLOSE_HANDLE(_h_thread);
        break;
      }
    }
  }
  _dw_thread_id = 0;
}

Это все прекрасно работает, пока не закрывается главное приложение. Я пока сделал закрытие потока в OnDestroy(), но это как-то, IMHO, по-ламерски... А хотелось бы сделать правильно и красиво :-)

Кстати, даже этот предупреждающий MessageBox не выскакивает! Только компьютер "пикает"... :-)

Записан

MPEG-4 - в массы!
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 18-03-2009 11:26 » 

OMG Улыбаюсь
Может, лучше всё-таки не варварский sleep в цикле, пока хэндл не обнулится, а штатный WaitForSingleObject на хэндл потока? И закрывать этот самый хэндл после завершения ожидания.
Записан
jur
Помогающий

lt
Offline Offline

« Ответ #5 : 18-03-2009 11:53 » 

Может, лучше всё-таки не варварский sleep в цикле, пока хэндл не обнулится, а штатный WaitForSingleObject на хэндл потока? И закрывать этот самый хэндл после завершения ожидания.

В-общем насчет Sleep-а согласен :-) Однако, без него - все намного хуже...

Переделал закрытие потока так:

Код:
void CThread::close_thread()
{
  SetEvent(_h_shutdown_event);
  if(WaitForSingleObject(_h_thread,1000) == WAIT_TIMEOUT) {
    MessageBox(NULL,"  Cannot close thread  ","Start()",MB_OK|MB_APPLMODAL|MB_TOPMOST);
  }
  SAFE_CLOSE_HANDLE(_h_thread);
  _dw_thread_id = 0;
}

Так теперь этот WaitForSingleObject вообще ничего не отслеживает... Можно SetEvent(_h_shutdown_event); вообще выбросить, все-равно таймаута нет, все будто-бы прекрасно (без этого ивента только нормальное завершение потока выдает предупреждение, а при выгрузке DLL-ки - не выдает).

Для меня такая ситуация весьма опасна, т.к. мне обязательно нужно не просто убить функцию потока, а именно корректно ее завершить! (В одной из DLL-ок запускается считывание данных из драйвера USB в четыре очереди. Если их корректно не закрыть - можно вплоть до BSOD-а доиграться...) А при WaitForSingleObject я даже не могу узнать, что функция потока не завершилась... Просто померла "прекрасная маркиза" и все дела... ;-)

Записан

MPEG-4 - в массы!
jur
Помогающий

lt
Offline Offline

« Ответ #6 : 18-03-2009 18:50 » 


Нерешаемая задача, черт бы ее подрал... В основной программе сделал специальный класс - CWrapper - деструктор которого оснастил вызовом:

Код:
CWrapper::~CWrapper()
{
  ::OutputDebugString("CWrapper(): Stopped\n");
  Thread_probe_dll_Stop();
}

Зараза! Ни "Stopped", ни закрытия моего потока не происходит... Если нормально завершать программу - все происходит, а если прервать ее в отладчике (т.е. именно та ситуация, когда не вызывается этот треклятый OnDestroy()), то сливай воду... Как это так, что в случае какой-нить ошибки не вызывается деструктор?! Мрак...

Ну не может же такого быть, чтобы моя тривиальнейшая задача не имела решения!...

P.S. А у меня на очереди еще вопрос про ListControl... ;-)

Записан

MPEG-4 - в массы!
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 18-03-2009 18:59 » 

Погоди, а при чём тут прерывание в отладчике? Понятно, что если терминируешь программу из отладчика, она не завершится корректно. Прибьёт, и всё. Или ты имеешь в виду, что после остановок на breakpoint-ах перестаёт нормально завершаться?
Записан
jur
Помогающий

lt
Offline Offline

« Ответ #8 : 18-03-2009 19:19 » 

Я имею ввиду, что если в программе произойдет ошибка, то прервать ее можно только из отладчика (если программа запускалась, например, из-под Студии). Ну там переменную цикла неправильно изменяю, или еще что-то похожее... В этом случае мой критический поток не закроет свое считывание из USB, и с большой степенью вероятности я получу BSOD. BSOD-ов - не люблю... :-)

Вот чтобы побороть эти нежелательные ситуации я и решил разобраться, как же все-таки программа завершает работу (в том числе и нештатно). До сих пор я думал, что деструкторы вызываются всегда и всенепременно. А тут - на тебе...

Непонятно... Причем, чувствую, что это как-то решается, а как - Бог весть...

P.S. К тому же, по логике программистской не может так быть, чтобы отдельная DLL-ка не могла создать свой поток и не могла его корректно завершить! "Что же это такое, граждане!" (C) ...

P.P.S. Сдается мне, что тут что-то не того с фактом закрытия потока DLL-ки потоком основной программы. Может это можно как-то предотвратить? Секъюрными атрибутами, или еще как? IMHO, проблема именно в этом: ивент завершения взводится нормально, однако функция потока уже убита... Слушать уже некому... Соответственно, дорога BSOD-у открыта...

« Последнее редактирование: 18-03-2009 19:59 от jur » Записан

MPEG-4 - в массы!
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines