我们有几个 .NET 应用程序使用 FileSystemWatcher 监视目录中的新文件。文件从另一个位置复制,通过 FTP 等上传。当它们进入时,文件以一种或另一种方式处理。然而,一个我从未见过满意答案的问题是:对于大文件,如何知道被监视的文件何时仍在写入?显然,我们需要等到文件完成并关闭后再开始处理它们。FileSystemWatcher 事件中的事件参数似乎没有解决这个问题。
9 回答
如果您可以控制将文件写入目录的程序,则可以让程序将文件写入临时目录,然后将它们移动到监视目录中。移动应该是一个原子操作,所以观察者不应该看到文件,直到它完全在目录中。
如果您无法控制写入监视目录的内容,您可以在监视程序中设置一个时间,当文件在给定时间内保持相同大小时,文件被认为是完整的。如果不考虑立即处理,则将此计时器设置为相对较大的值是一种相当安全的方法,可以知道文件是否完整或永远不会。
在文件关闭之前,不应触发 FileSystemWatcher 上的“已更改”事件。请参阅我对类似问题的回答。随着新数据的进入,FTP下载机制有可能在下载过程中多次关闭文件,但我认为这不太可能。
除非可以验证文件的内容是否完成(它具有可验证的格式或包含内容的校验和),否则只有发送者可以验证整个文件是否已到达。
我过去曾使用锁定方法通过 FTP 发送大文件。
文件以替代扩展名发送,一旦发件人满意,文件就会被重命名。
以上显然与定期整理具有临时扩展名的旧文件的过程相结合。
另一种方法是创建一个具有相同名称但具有附加 .lck 扩展名的零长度文件。真实文件完全上传后,lck 文件将被删除。接收进程显然会忽略具有锁定文件名称的文件。
如果没有这样的系统,接收者永远无法确定整个文件已经到达。
检查 x 分钟内未更改的文件很容易出现各种问题。
以下方法尝试打开具有写入权限的文件。它将阻止执行,直到文件完全写入磁盘:
/// <summary>
/// Waits until a file can be opened with write permission
/// </summary>
public static void WaitReady(string fileName)
{
while (true)
{
try
{
using (System.IO.Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
if (stream != null)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
break;
}
}
}
catch (FileNotFoundException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
catch (IOException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
catch (UnauthorizedAccessException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
Thread.Sleep(500);
}
}
(来自我对相关问题的回答)
您是否尝试过对文件进行写锁定?如果它正在被写入,那应该会失败,并且你知道让它不理会一点......
您可能必须使用一些带外信号:让“file.ext”的生产者编写一个虚拟的“file.ext.end”。
+1 如果可能,使用 file.ext.end 信号器,其中 file.ext.end 的内容是较大文件的校验和。这不是为了安全,而是为了确保一路上没有乱码。如果有人可以将他们自己的文件插入到大流中,他们也可以替换校验和。
如果文件上传中途失败并且发件人尚未尝试重新发送(和重新锁定)文件,则写锁定将无济于事。
如果文件已由 ftp 完全上传,我在 Windows 中检查的方法是尝试重命名它。如果重命名失败,则文件不完整。我承认不是很优雅,但它确实有效。