我有一个应用程序,可以在主PC上每秒将信息记录到每日文本文件中。使用相同应用程序的网络上的从属PC希望将此文本文件复制到其本地驱动器。我可以看到存在文件访问问题。
这些文件各不应超过30-40MB。网络将是100MB以太网。我可以看到复制过程有可能花费超过1秒的时间,这意味着日志记录PC需要在读取文件时打开文件进行写入。
文件写入(日志记录)和文件复制过程的最佳方法是什么?我知道有标准的Windows CopyFile()过程,但这给了我文件访问问题。还有TFileStream使用fmShareDenyNone标志,但这也偶尔会给我一个访问问题(比如每周1次)。
这是完成此任务的最佳方法是什么?
我目前的档案记录:
procedure FSWriteline(Filename,Header,s : String);
var LogFile : TFileStream;
line : String;
begin
if not FileExists(filename) then
begin
LogFile := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
try
LogFile.Seek(0,soFromEnd);
line := Header + #13#10;
LogFile.Write(line[1],Length(line));
line := s + #13#10;
LogFile.Write(line[1],Length(line));
finally
logfile.Free;
end;
end else begin
line := s + #13#10;
Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
try
logfile.Seek(0,soFromEnd);
Logfile.Write(line[1], length(line));
finally
Logfile.free;
end;
end;
end;
我的文件复制程序:
procedure DoCopy(infile, Outfile : String);
begin
ForceDirectories(ExtractFilePath(outfile)); //ensure folder exists
if FileAge(inFile) = FileAge(OutFile) then Exit; //they are the same modified time
try
{ Open existing destination }
fo := TFileStream.Create(Outfile, fmOpenReadWrite or fmShareDenyNone);
fo.Position := 0;
except
{ otherwise Create destination }
fo := TFileStream.Create(OutFile, fmCreate or fmShareDenyNone);
end;
try
{ open source }
fi := TFileStream.Create(InFile, fmOpenRead or fmShareDenyNone);
try
cnt:= 0;
fi.Position := cnt;
max := fi.Size;
{start copying }
Repeat
dod := BLOCKSIZE; // Block size
if cnt+dod>max then dod := max-cnt;
if dod>0 then did := fo.CopyFrom(fi, dod);
cnt:=cnt+did;
Percent := Round(Cnt/Max*100);
until (dod=0)
finally
fi.free;
end;
finally
fo.free;
end;
end;
答案 0 :(得分:2)
我建议不要一遍又一遍地关闭和重新打开共享文件。由于你每秒都写到它,这只是不必要的开销。
在Master端,创建并关闭文件(fmCreate
标志不能与其他标志一起使用!),然后在fmOpenWrite
模式下以fmShareDenyWrite
共享重新打开它,保持开放状态,并在需要时写信给它。
在Slave端,以fmOpenRead
模式打开文件并共享fmShareDenyNone
,将其保持打开状态,然后每秒读取一次。无需每次都通过网络复制整个共享文件。这是浪费的带宽。只需读取过去几秒内写入的所有新数据即可。如果Slave需要将数据存储在本地文件中,那么它可以独立于共享文件管理单独的本地文件,在需要时将新数据推送到本地文件中。
答案 1 :(得分:1)
处理您特定的偶然重复出现的问题:
您没有说出您正在使用的Delphi版本。
TFileStream.Create()构造函数中有一个错误,包括2007版本(至少)。这可能会解释您的偶然并发问题。
话虽如此,我相信这个bug更有可能导致文件没有按预期创建(当另外指定ShareMode时),这可能反过来导致你的并发问题。
解决这个问题的一种方法可能是在需要创建文件时,首先创建文件然后只需将其打开以作为单独的构造函数调用进行写入 - 这实际上使文件创建成为一个单独的步骤,文件编写了一致的部分过程:
if not FileExists(filename) then
begin
// There may be a more efficient way of creating an empty file, but this
// illustrates the approach
LogFile := TFileStream.Create(FileName, fmCreate);
LogFile.Free;
line := Header + #13#10 + s + #13#10;
end
else
line := s + #13#10;
Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
try
logfile.Seek(0,soFromEnd);
Logfile.Write(line[1], length(line));
finally
Logfile.free;
end;
答案 2 :(得分:0)
使用标准Append文件创建/打开命令,使用write
更新日志,并立即close
文件。
使用操作系统上的作业复制/移动文件;让它重试并以超出您要求的频率发射。
如果你想从Delphi中做到这一点,那么使用MoveFile来移动整个事物。
您可能希望将日志写入和移动都包含在try-except
中,以便在文件系统(Windows上的NTFS?)无法解析并发性时,可以重试它们一段合理的次数。在最坏的情况下,要么:
如果操作系统无法解决竞争条件,那么您必须使用信号量/锁定优先考虑饥饿行为。
答案 3 :(得分:-1)
搜索名为“IsFileInUse”的函数或类似的东西,我相信你可以使用它:
// master
while IsFileInUse(*AFileName*) do
Sleep(10);
write-content-to-file
// slave
while IsFileInUse(*AFileName*) do
Sleep(10);
copy-file-to-a-special-location
和presto !!你做完了!!