Delphi:在Form =“Canvas不允许绘图”中的ZipForge被冻结

时间:2011-06-13 18:47:31

标签: multithreading delphi unzip

//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错误的情况下解压缩?谢谢!

1 个答案:

答案 0 :(得分:4)

  

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的所有代码都是线程不安全的,并且不是说从一个secundary线程内到主线程控件,组件或变量的每次调用都是危险的,但它是防止它的好习惯。对我来说,术语线程安全是非常谨慎的。但是这个不安全的实际原因是所有用户界面控件(即所有窗口GDI对象)只能与单个线程具有亲和力;主线。