Delphi - Помощь при вызове многопоточной функции dll из другого потока - PullRequest
1 голос
/ 22 января 2010

Я использую Delphi 2006 и у меня возникла небольшая проблема с разрабатываемым приложением.

У меня есть форма, которая создает поток, который вызывает функцию, которая выполняет длительную операцию, давайте назовем ее LengthyProcess. Внутри функции LengthyProcess мы также вызываем несколько функций Dll, которые также создают свои собственные потоки.

Проблема, с которой я сталкиваюсь, заключается в том, что если я не использую функцию Синхронизации моего потока для вызова LengthyProcess, поток перестает отвечать (основной поток все еще отвечает нормально). Я не хочу использовать Synchronize, потому что это означает, что основной поток ожидает завершения LengthyProcess и поэтому не может создать отдельный поток.

Я отследил проблему до функции внутри dll, которая создает поток и затем вызывает WaitFor, кстати, все это делается с помощью TThread. WaitFor проверяет, равен ли CurrentThreadID значению MainThreadID, и если это так, то он вызовет CheckSychronization, и все в порядке. Поэтому, если мы используем Synchronize, тогда CurrentThreadID будет равен MainThreadID, однако, если мы не используем Synchronize, то, конечно, CurrentThreadID <> MainThreadID, и когда это происходит, WaitFor сообщает текущему потоку (созданному мной потоку) дождаться потока, созданного DLL и так CheckSynchronization никогда не вызывается, и мой поток в конечном итоге ждет поток, созданный в DLL.

Надеюсь, это имеет смысл, извините, я не знаю лучшего способа объяснить это. Кто-нибудь еще имел эту проблему и знает, как ее решить, пожалуйста?

Ответы [ 3 ]

7 голосов
/ 22 января 2010

Если ваш вторичный поток «перестает отвечать», то я предполагаю, что у него есть сообщение pump.(В противном случае вам нужно объяснить, что он перестает отвечать на .) Похоже, вы также хотите, чтобы поток мог определить, когда завершается работа третичного потока.(«Первичным» потоком здесь является поток VCL, который вообще не задействован.)

Вы пытались использовать WaitFor, но были разочарованы, когда обнаружили, что он блокируется.Это то, что он всегда был предназначен для этого.Его поведение в главном потоке, где он становится странным, поэтому безопасно вызывать из потока VCL, даже если он никогда не предназначался для такого использования первоначально.

Для обработки сообщений и дождитесь окончания работы потоков, вам нужно использовать одну или несколько функций ожидания из Windows API.Начните с MsgWaitForMultipleObjects.Он может ожидать различные типы дескрипторов ядра, включая дескрипторы потоков, но также уведомлять вас о доступности сообщений.Идея состоит в том, что вы будете вызывать эту функцию в цикле.Когда он говорит, что сообщения доступны, обработайте их, а затем снова выполните цикл, чтобы продолжить ожидание.

Ниже приведен только набросок.Вы захотите проверить документацию по всем используемым функциям API и объединить ее с остальными знаниями о собственных потоках.

procedure TSecondaryThread.Execute;
var
  ret: DWord;
  ThreadHandle: THandle;
  Msg: TMsg;
begin
  ThreadHandle := TertiaryThread.Handle;
  repeat
    ret := MsgWaitForMultipleObjects(1, ThreadHandle, False, Infinite, qs_AllEvents);
    case ret of
      Wait_Object_0: begin
        // The thread terminated. Do something about it.
        CloseHandle(ThreadHandle);
        PostQuitMessage(0);
        // Put *something* in the parameter so further calls to MWFMO
        // will have a valid handle. May as well use a handle to something
        // that will never become signaled so all we'll get are more
        // messages. I'm pretty sure you can't pass an empty array of
        // handles; there must be at least one, and it must be valid.
        ThreadHandle := Self.Handle;
      end;
      Wait_Object_0 + 1: begin
        // At least one message is available. Handle *all* of
        // them before calling MsgWaitForMultipleObjects again
        while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
        case Msg.Message of
          wm_Quit: begin
            // Do something about terminating the tertiary thread.
            // Then stop the message loop and the waiting loop.
            Exit;
          end;
          else begin
            TranslateMessage(Msg);
            DispatchMessage(Msg);
          end;
        end;
      end;
      Wait_Timeout: Assert(False, 'Infinity has passed');
      Wait_Failed: RaiseLastOSError;
      else Assert(False, 'Unexpected return value');
    end;
  until False;
end;

Часть, касающаяся обработки all сообщения важны.Как только вы наберете GetMessage, PeekMessage или WaitMessage, ОС пометит все сообщения в очереди как "старые", но MsgWaitForMultipleObjects вернется только тогда, когда в очереди будет "новое" сообщение -тот, который прибыл после последнего вызова PeekMessage.

0 голосов
/ 01 июня 2010

Использование класса TThread или даже объекта Application в DLL-библиотеке Delphi крайне небезопасно. Базовые классы RTL и VCL, глобальные и одноэлементные объекты предназначены для существования один раз для каждого процесса и не могут обрабатывать дублирование пространства имен и неполную инициализацию в стандартной библиотеке DLL.
Вы можете обойти это путем сборки с помощью пакетов времени выполнения (достаточно RTL и VCL; вы также можете создать свой собственный, используя только те системные модули, которые вам действительно нужны), в EXE и во всех DLL, ссылающихся на модули времени выполнения (особенно формы и классы) - таким образом они получают единое общее пространство имен и полную последовательность инициализации EXE.
Если вы вообще не можете изменить DLL, вы можете попытаться установить для нее Application.Handle, MainThreadID, SyncEvent и WakeMainThread соответствующие значения в основном модуле EXE - это может работать, но это так же некрасиво, как выглядит, и не охватывает все крайние случаи (классы и важные глобальные переменные все равно будут дублироваться).

0 голосов
/ 24 января 2010

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

Все темы, описанные ниже, являются производными от TThread.

У меня есть форма, которая запускает поток, но не ждет его. Поток, запущенный формой, вызывает функцию, которая выполняет длинную задачу.

Функция вызывает другую функцию в DLL, функция в DLL запускает поток и ожидает его. Поток, запущенный функцией DLL, вызывает другую функцию через синхронизацию.

Form-> Запуск потока, но не ожидание-> Поток вызывает функцию-> Функция вызывает функцию DLL-> Функция Dll запускает поток и ожидает-> Поток, запущенный функцией DLL, вызывает другую функцию через синхронизацию, т.е. синхронизацию (UpdateRecords).

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

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

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

Я разыскал проблему, похоже, что объект TApplication, созданный dll, не обрабатывает сообщения и имеет дескриптор 0, как это произошло, я не знаю (я не писал DLL , он был написан кем-то другим), но это является причиной проблемы, потому что он никогда не обработает метод, вызванный в очереди, синхронизацией.

Ранее я упоминал, что если я вызываю функцию, которая выполняет длинный процесс из моего потока, используя синхронизацию, то приложение не блокирует блокировку. Это связано с тем, что основной поток будет отвечать за вызов функции, которая выполняет длинный процесс. Таким образом, длинная функция процесса вызывает функцию DLL, которая запускает другой поток, а затем вызывает WaitFor. WaitFor проверяет, является ли текущий поток основным потоком, и если это так, он обрабатывает вызовы методов, которые были поставлены в очередь синхронизацией, непрерывно в цикле, пока поток, ожидающий потока, не будет освобожден (т. Е. Метод в очередь через синхронизацию вызывается, и событие ожидания сигнализируется).

В WaitFor, если текущий поток не является основным потоком, WaitFor просто блокирует, пока поток, который он ожидает, не будет освобожден.

В любом случае, я ничего не могу сделать с объектом приложения в dll, потому что этот dll довольно сложен и используется большей системой. Я предполагаю, что могу выставить метод в dll, который может обрабатывать методы в очереди синхронизации, затем я могу вызвать этот метод из моего приложения, пока оно не используется.

В любом случае еще раз спасибо за вашу помощь, но я решил эту проблему сейчас.

...