Обратно извикване, указано в QueueUserAPC, не се извиква

В моя код използвам QueueUserAPC за прекъсване на основната нишка от текущата му работа, за да извикам първо някакво обратно извикване, преди да се върна към предишната му работа.

std::string buffer;
std::tr1::shared_ptr<void> hMainThread;
VOID CALLBACK myCallback (ULONG_PTR dwParam) {
    FILE * f = fopen("somefile", "a");
    fprintf(f, "CALLBACK WAS INVOKED!\n");
    fclose(f);
}
void AdditionalThread () {
    // download some file using synchronous wininet and store the
    // HTTP response in buffer
    QueueUserAPC(myCallback, hMainThread.get(), (ULONG_PTR)0);
}
void storeHandle () {
    HANDLE hUnsafe;
    DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), 
        GetCurrentProcess(), &hUnsafe, 0, FALSE, DUPLICATE_SAME_ACCESS);
    hMainThread.reset(hUnsafe, CloseHandle);
}
void startSecondThread () {
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)AdditionalThread, 0, 0, NULL);
}

storeHandle и startSecondThread са изложени на Lua интерпретатор, който работи в основната нишка заедно с други неща. Това, което правя сега, е

  1. извикване на storeHandle от моя Lua интерпретатор. DuplicateHandle връща ненулева стойност и следователно успява.
  2. извикване на startSecondThread от моя Lua интерпретатор. Допълнителната нишка се стартира правилно и QueueUserAPC връща ненулева стойност, заявявайки, че всичко е минало добре.
  3. доколкото разбрах QueueUserAPC, myCallback сега трябва да се извиква от основната нишка. Обаче не става.

Ако QueueUserAPC е правилният начин да постигна целта си (==> вижте моите други въпрос):

  • Как мога да накарам това да работи?

Ако трябва някакъв друг метод за прекъсване на основната нишка:

  • Какъв друг метод трябва да използвам? (Имайте предвид, че не искам да използвам метода на изтеглянев главната нишка за това като WaitForSingleObject или избиране. Искам допълнителната нишка да избутва-е своите данни направо в основната нишка, възможно най-скоро.)

person Etan    schedule 30.12.2009    source източник


Отговори (2)


Да, QueueUserAPC не е решението тук. Неговото обратно извикване ще се изпълнява само когато нишката блокира и програмистът изрично е разрешил изчакването да бъде алармирано. Това е малко вероятно.

Колебая се да публикувам решението, защото ще ви навлече огромни проблеми. Можете да приложите прекъсване на нишка с SuspendThread(), GetThreadContext(), SetThreadContext() и ResumeThread(). Ключът е да запазите стойността CONTEXT.Eip в стека на повикванията на нишката и да я замените с адреса на функцията за прекъсване.

Причината, поради която не можете да направите това, е, че ще имате ужасни проблеми с повторното влизане. Няма начин да познаете в коя точка на изпълнение ще прекъснете нишката. Възможно е да е точно в средата на своето мутиращо състояние, състоянието, от което се нуждаете толкова много, че обмисляте да направите това. Няма начин да не попаднете в този капан, не можете да го блокирате с mutex или какво ли още не. Освен това е изключително трудно да се диагностицира, защото ще работи толкова добре толкова дълго време, след което произволно ще се провали, когато времето за прекъсване просто се окаже неудачно.

Една нишка трябва да е в добре известно състояние, преди да може безопасно да изпълнява инжектиран код. Традиционният е споменаван много пъти преди: когато една нишка изпомпва цикъл на съобщения, тя имплицитно е неактивна и не прави нищо опасно. QueueUserAPC има същия подход, нишката изрично сигнализира на операционната система, че е състояние, в което обратното извикване може да бъде безопасно изпълнено. Както чрез блокиране (без изпълнение на опасен код), така и чрез задаване на флага bAlertable.

Една нишка трябва изрично да сигнализира, че е в безопасно състояние. Няма безопасен модел за бутане, само за дърпане.

person Hans Passant    schedule 30.12.2009

От това, което мога да разбера в MSDN, обратното извикване не се извиква, докато нишката не влезе в състояние на предупреждение и това се прави чрез извикване на SleepEx, SignalObjectAndWait, WaitForSingleObjectEx, WaitForMultipleObjectsEx или MsgWaitForMultipleObjectsEx.

Така че, ако наистина не искате да правите анкети, не мисля, че този метод е адаптиран към вашия случай.

Възможно ли е да внедрите "помпа за съобщения" (или по-скоро слушател на събития) във вашата основна нишка и да делегирате цялата му текуща работа на друга нишка? В този случай главната нишка чака всяко събитие, зададено от другите нишки.

person cedrou    schedule 30.12.2009