复制持续写入的日志文件时避免文件锁定的技术咨询
问题解答
关于CopyFile()的文件锁定问题
- 默认调用
CopyFile()时,系统会以独占读模式打开源文件(即不允许其他进程写入),这就是你遇到日志停止更新的核心原因:复制过程中源文件被锁定,写入进程无法获取写入权限,直到复制完成或锁定被意外释放。
为什么CopyFileExW加COPY_FILE_OPEN_SOURCE_FOR_WRITE仍出错?
你使用的COPY_FILE_OPEN_SOURCE_FOR_WRITE参数确实会让系统以允许写入共享的方式打开源文件,但测试中出现I/O 103错误,问题出在你的写入测试代码上:
- Delphi的
Append方法默认会请求独占写入权限,而此时源文件被复制进程以“允许读、允许写”的共享模式打开,两者权限冲突就会触发103错误(无法获取独占锁)。这不是CopyFileExW的问题,是测试代码的打开模式和复制进程的共享模式不匹配。
完全无锁定的文件复制实现方案
要实现真正的无锁定复制,核心是确保源文件打开时允许所有共享操作(读、写、删除),以下两种方案可解决问题:
方案1:修正CopyFileExW搭配写入逻辑
保持复制端的参数不变,调整写入进程的文件打开方式,避免请求独占锁:
- 复制端继续使用
CopyFileExW(PChar(source), PChar(dest), nil, nil, nil, COPY_FILE_OPEN_SOURCE_FOR_WRITE); - 写入端替换
AssignFile为API级操作,用CreateFileW指定全共享模式:procedure TForm1.Button1Click(Sender: TObject); var hFile: THandle; buf: string; bytesWritten: DWORD; begin inc(i); buf := '['+FormatDateTime('yyyy-MM-dd hh:nn:ss',now)+'] '+'WRITE TEST'+' '+i.ToString + #13#10; hFile := CreateFileW(PWideChar('\\xxxx\xx\xx\xx\xx\xx\HJobs4.trc'), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); try if hFile = INVALID_HANDLE_VALUE then begin Memo1.Lines.Add(DateTimeToStr(now)+' 打开失败,错误码:'+IntToStr(GetLastError)); Exit; end; // 移动文件指针到末尾 SetFilePointer(hFile, 0, nil, FILE_END); WriteFile(hFile, PChar(buf)^, Length(buf), bytesWritten, nil); finally if hFile <> INVALID_HANDLE_VALUE then CloseHandle(hFile); end; end;
方案2:手动实现无锁定复制(更灵活可控)
完全绕过CopyFile系列函数,手动分块复制,精准控制源文件共享权限:
function CopyFileUnlocked(const src, dest: string): Boolean; var hSrc, hDest: THandle; buf: array[0..4095] of Byte; bytesRead, bytesWritten: DWORD; begin Result := False; // 打开源文件:允许所有共享操作 hSrc := CreateFileW(PWideChar(src), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN, 0); if hSrc = INVALID_HANDLE_VALUE then Exit; try // 创建目标文件 hDest := CreateFileW(PWideChar(dest), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if hDest = INVALID_HANDLE_VALUE then Exit; try // 分块复制 while ReadFile(hSrc, buf, SizeOf(buf), bytesRead, nil) and (bytesRead > 0) do begin if not WriteFile(hDest, buf, bytesRead, bytesWritten, nil) then Break; end; Result := (bytesRead = 0); finally CloseHandle(hDest); end; finally CloseHandle(hSrc); end; end;
该方法完全由你控制源文件的共享权限,确保复制期间其他进程可正常读写源文件。
额外注意事项
- 复制持续写入的日志文件时,副本无法包含复制过程中新增的内容;若需获取某时间点的完整快照,可考虑使用Windows卷影复制服务(VSS),但会增加实现复杂度;
- 处理网络共享文件时,需确保SMB协议的共享锁机制兼容,两端进程都遵循共享权限规则。
内容的提问来源于stack exchange,提问作者user1937012
相关产品推荐
相关产品推荐

