Символ «Резюме» устарел / Ошибка потока: недопустимый дескриптор (6) - PullRequest
9 голосов
/ 20 июля 2011

У меня есть старый фрагмент кода, который я хочу обновить до Delphi XE.У меня есть предупреждение компилятора о Resume, и я хочу заменить его на Start, но программа падает.

constructor THTTPGetThread.Create(aAcceptTypes, aAgent, aURL, aFileName, aUserName, aPassword, aPostQuery, aReferer: String; aBinaryData, aUseCache: Boolean; aProgress: TOnProgressEvent; aToFile: Boolean);
begin
  FreeOnTerminate := True;
  inherited Create(True);

  FTAcceptTypes := aAcceptTypes;
  FTAgent       := aAgent;
  FTURL         := aURL;
  FTFileName    := aFileName;
  FTUserName    := aUserName;
  FTPassword    := aPassword;
  FTPostQuery   := aPostQuery;
  FTReferer     := aReferer;
  FTProgress    := aProgress;
  FTBinaryData  := aBinaryData;
  FTUseCache    := aUseCache;
  FTToFile      := aToFile;

  Resume;      <------------ works, but I get compiler warning
  //Start;     <------------ doesn't work
end;

Ошибка, которую я получаю при использовании START: «Ошибка потока: дескрипторinvalid (6) ".
Я не хочу сложных вещей (заморозить / синхронизировать потоки).Я просто хочу скачать файл из интернета, не блокируя графический интерфейс.

Ответы [ 2 ]

15 голосов
/ 20 июля 2011

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

Обратите внимание, что поток не запускается до тех пор, пока все конструкторы не завершатся, поэтому значение идентично опубликованному коду.


Что касается того, почему ваш код не работает, посмотрите на следующие выдержки из источника:

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended and not FExternalThread then
    InternalStart(True);
end;

procedure TThread.InternalStart(Force: Boolean);
begin
  if (FCreateSuspended or Force) and not FFinished and not FExternalThread then
  begin
    FSuspended := False;
    FCreateSuspended := False;
    if ResumeThread(FHandle) <> 1 then
      raise EThread.Create(SThreadStartError);
  end
  else
    raise EThread.Create(SThreadStartError);
end;

procedure TThread.Start;
begin
  InternalStart(False);
end;

Ваш код вызывает унаследованный конструктор с CreateSuspended=True.Это устанавливает FCreateSuspended равным True.Затем вы звоните Start до запуска TThread.AfterConstruction.Это успешно запускает поток, но, что особенно важно, оно сбрасывает FCreateSuspended в False.Затем, когда TThread.AfterConstruction он пытается возобновить поток, который завершается сбоем, потому что он уже запущен.

Я думаю, что код Delphi хорош, потому что некорректно вызывать Start из конструктора.Вы должны быть уверены, что все конструкторы запустились, а конструкторы производных классов запустятся после вашего вызова Start.У вас еще нет никаких производных классов, но дело не в этом - дело в том, что вызов Start из конструктора не поддерживается.

Суть в том, что вы должны создать этот поток без приостановки и позволитьStart звоните от вашего имени от AfterConstruction.

7 голосов
/ 20 июля 2011

Проблема связана с механикой, используемой для обеспечения того, что поток не запустится, пока не будут выполнены все конструкторы (как упомянуто Дэвидом).

Видите ли, все потоки фактически создаются в приостановленном состоянии, независимо от того, что вы передаете в качестве аргумента конструктору. Это сделано для того, чтобы поток фактически не запускался до того, как конструктор завершил свою работу. Запуск потока выполняется, как только все конструкторы завершены в функции AfterConstruction (если CreateSuspended имеет значение false). Вот соответствующая часть кода для вашей проблемы:

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended and not FExternalThread then
    InternalStart(True);
end;

procedure TThread.Start;
begin
  InternalStart(False);
end;

procedure TThread.InternalStart(Force: Boolean);
begin
  if (FCreateSuspended or Force) and not FFinished and not FExternalThread then
  begin
    FSuspended := False;
    FCreateSuspended := False;
    if ResumeThread(FHandle) <> 1 then
      raise EThread.Create(SThreadStartError);
  end
  else
    raise EThread.Create(SThreadStartError);
end;

Что происходит в вашей ситуации, когда вы звоните Start, он работает нормально. Он вызывает InternalStart, который очищает флаг FSuspended и FCreateSuspended, а затем возобновляет поток. После этого после завершения конструктора выполняется AfterConstruction. AfterConstruction проверяет FCreateSuspended (который уже был очищен при вызове Start) и пытается запустить поток, но поток уже запущен.

Почему звонит резюме работает? Возобновить не снимите флаг FCreateSuspended.

Итак, чтобы подвести итог, если вы хотите, чтобы ваш поток запускался автоматически после его создания, просто передайте False параметру CreateSuspended конструктора TThread, и он будет работать как шарм.

Что касается причины, по которой Resume / Suspend устарели ... Моя лучшая догадка - это плохая практика в первую очередь приостанавливать поток (помимо создания), потому что не было никакой гарантии состояния потока, когда он приостановлен , Это может быть блокировка ресурса, между прочим. Если в указанном потоке есть очередь сообщений, он перестанет отвечать на них, что вызовет проблемы для процесса, полагающегося на широковещательное сообщение, например, при использовании ShellExecute для открытия URL-адреса (по крайней мере, в Internet Explorer, не уверен, что затронуты другие браузеры). Таким образом, они устарели Resume / Suspend и добавили новую функцию в «Resume» потока, который был создан приостановленным (то есть Start).

...