Delphi: ZipForge в потоке = Canvas не позволяет рисовать, форма зависла

//Thread
Procedure StartUpdating.UnZip;
begin
form2.ZipForge1.FileName := ItemToExtract;
form2.ZipForge1.OpenArchive;
form2.ZipForge1.BaseDir := XXX;
form2.ZipForge1.ExtractFiles('*.*');
form2.ZipForge1.CloseArchive;
end;

PROCEDURE StartUpdating.Execute;
begin
UnZip; // on ZipForge1Password I get error: EInvalidOperation with message 'Canvas does not allow drawing'. The Form is not frozen while archive is being extracted.
Synchronize(UnZip); //  No EInvalidOperation error on ZipForge1Password, but the Form is frozen while archive is being extracted.
end;

procedure TForm2.ZipForge1Password(Sender: TObject; FileName: string;
  var NewPassword: AnsiString; var SkipFile: Boolean);
var  s:string;
begin

if PassSkip then SkipFile:=true else
    begin
    if InputQuery('Pass',FileName, s) then NewPassword:=ansistring(s) else //I suppose EInvalidOperation error is here
        begin
          PassSkip:=true;
          SkipFile:=true;
          ThreadUpdating.Terminate;
        end;
    end;
end;

Как разархивировать без замороженной формы и без ошибки EInvalidOperation? Спасибо!


person maxfax    schedule 13.06.2011    source источник


Ответы (1)


UnZip; // on ZipForge1Password I get error: EInvalidOperation with message 'Canvas does not allow drawing'. The Form is not frozen while archive is being extracted.

Это связано с тем, что вы запускаете небезопасный для потока код (1) из своего потока. Весь небезопасный для потоков код (1) (как и большинство подпрограмм VCL и WinAPI) должен выполняться в основном потоке. Подпрограмма UnZip ссылается на form2, который является компонентом VCL, поэтому вы должны (1) использовать Synchronize, чтобы временно передать выполнение основному потоку.

Synchronize(UnZip); // No EInvalidOperation error on ZipForge1Password, but the Form is frozen while archive is being extracted.

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

Итак, теперь у вас есть небольшая дилемма курицы и яйца. Тот, который можно устранить, создав компонент ZipForge @runtime в вашем потоке. В этом случае единственное взаимодействие с пользователем, которое остается синхронизированным, — это предоставление пароля. Недостатком является то, что Synchronize принимает только метод без параметров, поэтому вам придется немного поработать над реализацией обработчика событий OnPassword. Это может выглядеть следующим образом:

type
  TUnZip = class(TThread)
  private
    FFileName: String;
    FPassword: AnsiString;
    FSkipFile: Boolean;
    procedure DoPassword;
    procedure ZipForgePassword(Sender: TObject; FileName: string;
      var NewPassword: AnsiString; var SkipFile: Boolean);
  protected
    procedure Execute; override;
  public
    property PassSkip ...
    property ItemToExtract ...
  end;

{ TUnZip }

procedure TUnZip.DoPassword;
var
  S: String;
begin
  if PassSkip then
    FSkipFile := True
  else if InputQuery('Pass', FFileName, S) then
    FPassword := AnsiString(S)
  else
  begin
    PassSkip := True;
    FSkipFile := True;
    Terminate;
  end;
end;

procedure TUnZip.Execute;
var
  ZipForge: TZipForge;
begin
  ZipForge := TZipForge.Create(...);
  try
    ZipForge.OnPassword := ZipForgePassword;
    ZipForge.FileName := ItemToExtract;
    ZipForge.OpenArchive;
    if not Terminated then  {Assuming OpenArchive triggers the OnPassword event}
    begin
      ZipForge.BaseDir := XXX;
      ZipForge.ExtractFiles('*.*');
      ZipForge.CloseArchive;
    end;
  finally
    ZipForge.Free;
  end;
end;

procedure TUnZip.ZipForgePassword(Sender: TObject; FileName: String;
  var NewPassword: AnsiString; var SkipFile: Boolean);
begin
  FFileName := FileName;
  FPassword := NewPassword;
  FSkipFile := SkipFile;
  Synchronize(DoPassword);
  FileName := FFileName;
  NewPassword := FPassword;
  SkipFile := FSkipFile;
end;

Отказ от ответственности: я не знаком с компонентом ZipForge, поэтому этот код может быть неполным. Вы должны реализовать все настройки, созданные во время разработки, в TUnZip.Execute. Я даже не знаю, есть ли вообще у TZipForge событие OnPassword, но я сделал это из вашего кода.

Изменить:

(1) Не весь код VCL является небезопасным для потоков, и не говорится, что каждый вызов из вторичного потока элемента управления, компонента или переменной основного потока опасен, но это хорошая практика. предотвратить это. На мой взгляд, термин потокобезопасность является удобным предостережением. Но фактическая причина этой небезопасности заключается в том, что все элементы управления пользовательского интерфейса (т. е. все объекты GDI Windows) могут иметь привязку только к одному потоку; основной поток.

person NGLN    schedule 13.06.2011
comment
@NGLN теперь, если это будет принятый ответ, не могли бы вы исправить фактические ошибки. Проблема не в отсутствии безопасности потоков, проблема в сходстве потоков элементов управления пользовательского интерфейса. И оценка полей объекта формы в порядке, проблема заключается в отображении модального диалога ввода. - person David Heffernan; 14.06.2011
comment
@David Да, я знаю, что вы можете получить доступ к формам, элементам управления и т. д., если вы просто читаете. Но для уверенности, что я никогда не прикасаюсь к хитрому геттеру, дескриптору WinAPI или ресурсу, я всегда стараюсь вообще не прикасаться к VCL и, следовательно, к любому основному блоку потока. Факт остается фактом: VCL не является потокобезопасным. Особенно все, полученное от TControl, является потенциальным кошмаром во вторичных потоках, и недобросовестное использование может привести к условиям гонки, взаимоблокировкам, перезаписи ссылок на компоненты, недопустимым счетчикам ссылок дескрипторов, повреждению данных и общих переменных и многому другому. [/напыщенный] - person NGLN; 14.06.2011
comment
Хорошо, я удалил свой ответ сейчас, так как нет смысла в том, чтобы двое говорили в основном одно и то же. - person David Heffernan; 14.06.2011