Нарушение прав доступа, вызванное завершением работы компонента Indy SSL (PPL / TTask)

Я использую версию Delphi (Delphi 10.2u2)

Моя цель - поиграть с веб-сервисом для обработки многопоточных команд GET и POST через платформу Indy, ничего сложного, и все работает нормально, пока я не закрою приложение во время запроса.

Для этого я использую TidHTTPClient для обработки команд и TIdSSLIOHandlerSocketOpenSSL для поддержки шифрования трафика.

Для многопоточности я использую TTask из библиотеки параллельного программирования (PPL). «Я новичок в использовании этой библиотеки с Delphi».

Вот пример кода:

procedure TForm3.Button1Click(Sender: TObject);
begin
TTask.Create(
              procedure
              var HTTP : TidHTTP;
                  SSL : TIdSSLIOHandlerSocketOpenSSL;
                  content : String;
              begin
                HTTP := TIdHTTP.Create(nil);

                SSL := TIdSSLIOHandlerSocketOpenSSL.create(nil);

                SSL.SSLOptions.Method := sslvSSLv23;

                HTTP.IOHandler       := SSL;
                HTTP.ConnectTimeout  := 20000;
                HTTP.ReadTimeout     := 60000;
                HTTP.HandleRedirects := true;

                try
                  {
                    If we close the application during the Get, it raise an exception.
                    This is because the SSL Library is freed before the get has the time to stop

                    finalization
                      UnLoadOpenSSLLibrary(); // called before get is over
                  }
                  Content := HTTP.Get('https://www.google.com/');

                  TThread.Synchronize(nil, procedure begin
                     memo1.text := Content;
                  end);
                except
                  // ...
                  on E : Exception do
                    TThread.Synchronize(nil, procedure begin
                      memo1.Text := 'Error';
                    end);
                end;

                SSL.Free;
                HTTP.Free;
  end).Start();
end;

Если вы скомпилируете и выполните этот код, вы увидите, что все работает нормально, пока не решите закрыть программу во время запроса (GET, POST или что-то еще).

Ошибка может варьироваться в зависимости от точного момента, когда вы закрываете приложение во время HTTP-запроса. Это будет нарушение прав доступа, ошибка Winsock2 (WSA ...) и т. Д.

Вот пример ошибки:  Нарушение прав доступа

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

Действительно, поскольку библиотеки SSL / Winsock (особенно) освобождаются во время незавершенного HTTP-запроса, непрерывность запроса вызовет некоторую недоступную функцию и вызовет исключение нулевого указателя, нарушение доступа и т. Д.

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

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

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

НО, когда я не могу найти способ сделать это, не вызывая исключения другого типа, например «Операция отменена»

procedure TForm3.FormDestroy(Sender: TObject);
begin
  ATask.Cancel;
  repeat
  if ATask.Wait(500) = false then begin
     CheckSynchronize();
  end;
 until (ATask = nil);
end;

Приведенный выше пример не сработает ...

Я уверен, что делаю что-то не так, я не мог найти никакой помощи, связанной с этой темой, в другом месте, Delphi PPL звучит много нового или моя проблема действительно особенная.

Заранее большое спасибо за вашу помощь.

Я действительно хочу использовать PPL для одного из моих самых больших проектов, я не могу развернуть приложение с такими сообщениями об ошибках ^ _ ^

С уважением,


person Phr0zeN    schedule 13.11.2018    source источник
comment
Подождите, пока все мои TTasks завершат свои задачи - посмотрите TTask.WaitForAll(). TTask реализует ITask интерфейс. Создайте массив из запущенных ITask объектов, а затем вы можете WaitForAll() в этом массиве перед выходом из приложения.   -  person Remy Lebeau    schedule 13.11.2018


Ответы (1)


Спасибо, что поставил меня на путь Реми. Вот полное решение проблемы.

procedure TForm3.FormDestroy(Sender: TObject);
begin
  while not TTask.WaitForAll(FTasks, 1000) do
    CheckSynchronize();
end;
person Phr0zeN    schedule 13.11.2018
comment
Цикл должен быть while not TTask.WaitForAll() - person David Heffernan; 14.11.2018
comment
Я согласен, что это экономит несколько строк кода. Моя версия - это рабочая альтернатива, которая позволяет вам обрабатывать другие задачи каждый раз, когда вы собираетесь выполнить цикл для функции WaitForAll (например, ведение журнала) - person Phr0zeN; 14.11.2018
comment
Очевидно, что в теле цикла вы можете делать все, что хотите, включая вызов CheckSynchronize. На самом деле, вместо того, чтобы объяснять это вам в комментарии, я отредактирую ваш ответ. Подожди секунду. - person David Heffernan; 14.11.2018