copy大文件问题 ( 积分: 100 )

  • 主题发起人 主题发起人 wuan
  • 开始时间 开始时间
W

wuan

Unregistered / Unconfirmed
GUEST, unregistred user!
我用文件流的方法完成文件copy,还想在主窗体上显示进度(注意为了提高速度直接应用的f2.copyfrom(f1,0)而不是f.readbuf与f.writebuf)于是我将f2.copyfrom(f1,0)放入副线程,基本可以实现小文件copy,但发现文件超过100M就会出现副线程意外终止错误而无法继续,每次错误出现的位置不定(有时是考到40%处,有时是考到80%处。。。有时可以完成)
说明一下:如果用单线程f2.copyfrom(f1,0)文件很大也没问题。
下面是代码,请高手指出原因:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Buttons, ComCtrls, ExtCtrls, StdCtrls, Gauges;
type
TForm1 = class(TForm)
Panel1: TPanel;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
GroupBox1: TGroupBox;
Edit1: TEdit;
Edit2: TEdit;
Source: TBitBtn;
BitBtn2: TBitBtn;
opd: TOpenDialog;
svd: TSaveDialog;
pb1: TGauge;
procedure SpeedButton3Click(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure SourceClick(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
f1,f2:Tfilestream;
s,t:string;
implementation
uses
unit2;
{$R *.dfm}
var
th:mypath;
j:bool;
procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
speedbutton2.Enabled:=true;
s:=edit1.text;
t:=edit2.text;
try
f1:=TFILEStream.Create(s,fmopenread);
Pb1.MaxValue:=f1.Size;
f2:=tfilestream.Create(t,fmopenwrite or fmcreate);
th:=mypath.Create(true);
th.Resume;
while pb1.Progress<pb1.MaxValuedo
begin
application.ProcessMessages;
pb1.Progress:=f2.size;
end;
speedbutton2.Enabled:=false;
showmessage('hh');
th.Destroy;
finally
f1.Free;
f2.free;
end;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
close;
end;

procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
j:=not j;
if j then
begin
speedbutton2.Caption:='&amp;contious';
th.Suspend;
end
else
begin
speedbutton2.Caption:='&amp;pause';
th.Resume;
end;
end;

procedure TForm1.SourceClick(Sender: TObject);
begin
if opd.Execute then
edit1.Text:=opd.FileName;
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
begin
svd.DefaultExt:=extractfileext(edit1.Text);
if svd.Execute then
edit2.Text:=svd.FileName;
end;

end.
unit Unit2;
interface
uses
Classes,unit1;
type
mypath = class(TThread)
private
{ Private declarations }
protected
procedure Execute;
override;
end;

implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure mypath.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end;
}
{ mypath }
procedure mypath.Execute;
begin
try
unit1.f2.CopyFrom(unit1.f1,0);
finally
end;
end;

end.
 
Function TFrm_FileCopy.mycopyfile(FormFileName,ToFileName:string):boolean;
var
FromF, ToF: file;
NumRead, NumWritten: Longword;
Buf: array[1..2048] of Char;
maxsize,copysize:Longword;
begin
Result:=True;
Try
{$I-}
AssignFile(FromF, FormFileName);
FileMode := 0;
{Set file access to read only }
Reset(FromF, 1);
{ Record size = 1 }
// Reset(FromF);
{ Record size = 1 }
AssignFile(ToF, ToFileName);
{ Open output file }
Rewrite(ToF, 1);
{ Record size = 1 }
maxsize :=FileSize(FromF) div 2048 ;
copysize:=0;
repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
copysize:=copysize+ 1 ;
TeProgressBar_File.Position :=Longword(copysize * 100 div maxsize );//这里显示进度
application.ProcessMessages ;
//把操作权限给系统
until (NumRead = 0) or (NumWritten <> NumRead);
CloseFile(FromF);
CloseFile(ToF);
Except
Result:=False;
end;
end;
 
经常拷贝 1G以上文件,一切正常
 
to flfqnet:
这种方法我以前用过,速度不如流快,请再看一下题.呵呵!
 
用copyfile快点吧
 
while pb1.Progress<pb1.MaxValuedo
begin
application.ProcessMessages;
//pb1.Progress:=f2.size;
把这句注释掉就可以拷贝完成
end;
 
to mooleychean:
是的,那就无法显示进度了,如果那样用单线程就可以了。我用双线程的目的就是想显示进度。
 
to jjtl:
我主要目的不是copy一个文件,而是想以尽可能快的速度断点续考一个文件,上述代码如果大文件问题解决了,只需改动一小点就可以实现续考了。
 
好,你是想知其然呢还是知其所以然,如果只是想知其然的话,告诉你一个方法,关键是不用Size返回拷贝进度,如下:
procedure TForm1.SpeedButton3Click(Sender: TObject);
var
sr: TSearchRec;
begin
.....
while pb1.Progress<pb1.MaxValuedo
begin
application.ProcessMessages;
//pb1.Progress:=f2.size;
把这句注释掉就可以拷贝完成
if Findfirst(t,faAnyFile,sr)=0 then
//加上这段代码
pb1.Progress:=sr.Size
else
pb1.Progress:=0;
Findclose(sr);
end;
end;
.....
end;

进度显示会有一点慢。
 
请大家指出我的代码中不健全的地方和改进方法。
 
最主要的问题是架构不合理。
 
to moolleychean
我试了一下,第一次copy一个120M的文件,没有问题但是进度条不连续,速度还慢,第二次copy一个334M的文件,在94%处线程终止,第三次copy该334M的文件,在99%处线程终止,就是说问题依旧。时间关系我没有再试。
能详细说明一下所以然吗?谢谢你。
 
我这边试了200多M到500多M的,很正常(Delphi2006+WindowsXP SP1)。
f2.size看上去好像只是取f2的大小,实际上:
f2.Size>GetSize>Seek>FileSeek(Hadnle,...)>SetFilePointer(Handle,...)
f2.CopyFrom>WriteBuffer>Write>FileWrite(Handle,...)>WriteFile(Handle,...)
最终都在对同一个文件Handle进行操作,如果是在多线程里,如果不出错那是幸运。
所以以你上面的架构,绝对不能通过读f2.size来实现进度条滚动。
想绝对可靠的办法的话,写一个TYourFileStream的继承类覆盖CopyFrom方法,因为CopyFrom实际上也是分段WriteBuffer的,分段大小为MaxBufSize = $F000,所以你可以完全Copy这个方法,只是在拷贝完一段时产生一个事件,将已经拷贝的字节数传出就可以了,CopyForm源码如下(注意我的注释):
function TStream.CopyFrom(Source: TStream;
Count: Int64): Int64;
const
MaxBufSize = $F000;
var
BufSize, N: Integer;
Buffer: PChar;
begin
if Count = 0 then
begin
Source.Position := 0;
Count := Source.Size;
end;
Result := Count;
if Count > MaxBufSize then
BufSize := MaxBufSize else
BufSize := Count;
GetMem(Buffer, BufSize);
try
while Count <> 0do
begin
if Count > BufSize then
N := BufSize else
N := Count;
Source.ReadBuffer(Buffer^, N);
WriteBuffer(Buffer^, N);
Dec(Count, N);
//Count为剩下的字节数,在这里加入一点代码,产生一个事件将Count值传出已方便进度条的显示,如下:
//if Assigned(FCopyEvent) then
// FCopyEvent(Self, Count);
//通知事件定义为TCopyEvent = procedure (Sender: TObject;
LeftBytes: Integer);
//在TYourFileStream的私有域定义变量FCopyEvent: TCopyEvent;
//在公有域定义属性property OnCopyEvent: TCopyEvent read FCopyEvent write FCopyEvent;
end;
finally
FreeMem(Buffer, BufSize);
end;
end;

然后怎么做应该很清楚了。
 
谢谢,我有空试试,分先给你。
 
后退
顶部