监视文件夹并在创建完成后复制新文件。

11 浏览
0 Comments

监视文件夹并在创建完成后复制新文件。

我建立了一个控制台应用程序,在Windows 2019服务器上监视一组文件夹,并将任何新创建的.txt文件复制到另一个文件夹,使用相同的文件名。目前它已经能够实现基本功能。现在我需要处理的是,大多数情况下这些文件很大,需要几分钟才能完成创建。我查阅了几篇Stack Overflow的帖子,并拼凑出以下代码来尝试实现这个功能:

using System;
using System.IO;
namespace Folderwatch
{
    class Program
    {
        static void Main(string[] args)
        {
            string sourcePath = @"C:\Users\me\Documents\SomeFolder";
            FileSystemWatcher watcher = new FileSystemWatcher(sourcePath);
            watcher.EnableRaisingEvents = true;
            watcher.IncludeSubdirectories = true;
            watcher.Filter = "*.txt";
            // 添加事件处理程序
            watcher.Created += new FileSystemEventHandler(OnCreated);
        }
        // 定义事件处理程序
        private static void OnCreated(object source, FileSystemEventArgs e)
        {
            // 指定文件创建时的操作
            FileInfo file = new FileInfo(e.FullPath);
            string wctPath = e.FullPath;
            string wctName = e.Name;
            string createdFile = Path.GetFileName(wctName);
            string destPath = @"C:\Users\SomeOtherFolder";
            string sourceFile = wctPath;
            string destFile = Path.Combine(destPath, createdFile);
            WaitForFile(file);
            File.Copy(sourceFile, destFile, true);
        }
        public static bool IsFileLocked(FileInfo file)
        {
            try
            {
                using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    stream.Close();
                }
            }
            catch (IOException)
            {
                // 文件不可用,因为它正在被写入、被其他线程处理或者已经存在(已被处理)
                return true;
            }
            // 文件未被锁定
            return false;
        }
        public static void WaitForFile(FileInfo filename)
        {
            // 这将阻塞执行,直到文件准备就绪
            // TODO: 添加一些逻辑使其异步和可取消
            while (!IsFileLocked(filename)) { }
        }
    }
}

我在`OnCreated`方法中尝试的是检查并等待文件创建完成,然后将文件复制到另一个目标位置。我似乎不知道如何使用`WaitForFile(file)`这一行 - 如果我注释掉这行并且文件创建是瞬间完成的,那么文件会按预期复制。如果我使用`WaitForFile`这一行,就什么都不会发生。我从Stack Overflow的其他帖子中获取了`IsFileLocked`和`WaitForFile`方法,但显然没有正确实现它们。

我注意到了这个PowerShell版本的答案Copy File On Creation (once complete),我不确定这个答案是否能给我指引,因为我对PowerShell的了解甚至不如对C#的了解。

编辑#1:我应该在接受答案之前测试更长的时间 - 程序运行大约一分钟后,我收到了以下错误,然后程序崩溃:

Unhandled exception. System.IO.IOException: The process cannot access the file
'C:\Users\me\Dropbox\test1.log'
because it is being used by another process.

任何关于这个问题的建议将不胜感激。当我进一步分析这些文件夹中的文件时,我发现其中一些是实时写入的日志文件,所以可能在文件实际完成之前,文件被写入了几个小时。我想知道这里是否有`NotifyFilter`的某种作用?

0
0 Comments

问题的出现原因是WaitForFile()方法的bug,它当前在文件未锁定时等待(而不是相反)。此外,您需要一种确认文件是否存在的方法。实现这个简单的方法是将WaitForFile()方法更改为如下所示:

public static bool WaitForFile(FileInfo file)
{
    while (IsFileLocked(file))
    {
        // 文件无法访问。让我们检查它是否存在。
        if (!file.Exists) return false;
    }
    // 文件现在可以访问。
    return true;
}

这将在文件存在且不可访问时一直等待。

然后,可以按以下方式使用它:

bool fileAvailable = WaitForFile(file);
if (fileAvailable)
{
    File.Copy(sourceFile, destFile, true);
}

这种方法的问题是while循环会使线程一直忙碌,这会消耗大量的CPU资源,并且阻止程序在等待某个文件期间处理其他文件。因此,最好在每次检查之间使用异步等待。

将WaitForFile方法更改为:

public static async Task WaitForFile(FileInfo file)
{
    while (IsFileLocked(file))
    {
        // 文件无法访问。让我们检查它是否存在。
        if (!file.Exists) return false;
        await Task.Delay(100);
    }
    // 文件现在可以访问。
    return true;
}

然后,在OnCreated内部使用await:

private async static void OnCreated(object source, FileSystemEventArgs e)
{
    // ...
    bool fileAvailable = await WaitForFile(file);
    if (fileAvailable)
    {
        File.Copy(sourceFile, destFile, true);
    }
}

这个方法非常全面和深思熟虑!现在尝试一下!谢谢!

看起来这个方法很有效。干得好,再次感谢。

请查看我在原帖中的修改,我现在遇到了一个异常。

这很可能是由于竞态条件引起的(您检查并确认文件未锁定,并且即将开始复制它 > 另一个线程/进程锁定该文件并开始写入 > 您尝试开始复制 > 哎呀!),所以您可能需要在try-catch中包装File.Copy()调用,并决定在失败时程序应该做什么。相关帖子:1,2。

0