42

当将多个文件放入监视目录时,我遇到了 FileSystemWatcher 的问题。我想将文件放在目录中后立即对其进行解析。通常,第一个文件解析得很好,但是将第二个文件添加到目录会导致访问问题。有时,第一个文件甚至不解析。只有一个应用程序正在运行并监视此目录。最终,这个过程将在多台机器上运行,它们将监视一个共享目录,但只有一台服务器可以解析每个文件,因为数据被导入数据库并且没有主键。

这是 FileSystemWatcher 代码:

public void Run() {
  FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp");
  watcher.NotifyFilter = NotifyFilters.FileName;
  watcher.Filter = "*.txt";

  watcher.Created += new FileSystemEventHandler(OnChanged);

  watcher.EnableRaisingEvents = true;
  System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}

然后解析文件的方法:

private void OnChanged(object source, FileSystemEventArgs e) {
  string line = null;

  try {
    using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) {
      using (StreamReader sr = new StreamReader(fs)) {
        while (sr.EndOfStream == false) {
          line = sr.ReadLine();
          //parse the line and insert into the database
        }
      }
    }
  }
  catch (IOException ioe) {
    Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString());
  }

移动第二个文件时,它正在捕获

System.IO.IOException:该进程无法访问文件“C:\Temp\TestFile.txt”,因为它正被另一个进程使用。

如果它在多台机器上运行,我希望看到这个错误,但它现在只在一台服务器上运行。不应该有其他进程使用此文件 - 我创建了它们并将它们复制到应用程序运行时的目录中。

这是设置 FileSystemWatcher 的正确方法吗?我怎样才能看到这个文件上的锁是什么?为什么它不解析这两个文件 - 我必须关闭 FileStream 吗?我想保留 FileShare.None 选项,因为我只想要一个服务器来解析文件 - 获取文件的服务器首先解析它。

4

9 回答 9

54

这种方法的一个典型问题是在触发事件时文件仍在被复制。显然,您会得到一个异常,因为文件在复制过程中被锁定。大文件尤其容易出现异常。

作为一种解决方法,您可以先复制文件,然后重命名它并监听重命名事件。

或者另一种选择是有一个while循环检查文件是否可以用写访问权限打开。如果可以,您将知道复制已完成。C# 代码可能如下所示(在生产系统中,您可能希望有最大重试次数或超时而不是while(true)):

/// <summary>
/// Waits until a file can be opened with write permission
/// </summary>
public static void WaitReady(string fileName)
{
    while (true)
    {
        try
        {
            using (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);
    }
}

另一种方法是在复制完成后在文件夹中放置一个小的触发器文件。您的 FileSystemWatcher 只会监听触发文件。

于 2009-03-31T00:32:51.243 回答
10

我会在上面留下评论,但我还没有足够的积分。

这个问题的评价最高的答案有一段代码,如下所示:

using (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;
    }
}

使用FileShare.ReadWrite设置的问题是它请求访问文件基本上是说“我想读/写这个文件,但其他人也可以读/写它”。这种方法在我们的情况下失败了。接收远程传输的进程没有锁定文件,但它正在积极地写入文件。我们的下游代码(SharpZipLib)因“正在使用的文件”异常而失败,因为它试图用一个FileShare.Read(“我想要读取文件,并且只让其他进程也读取”)打开文件。因为打开文件的进程已经在写入,所以这个请求失败了。

但是,上面响应中的代码过于宽松。通过使用FileShare.ReadWrite,它成功地获得了对文件的访问权(因为它要求一个可以遵守的共享限制),但是下游调用继续失败。

调用中的共享设置File.Open应该是FileShare.ReadorFileShare.NoneNOT FileShare.ReadWrite

于 2011-09-21T21:33:24.440 回答
4

当您在 OnChanged 方法中打开文件时,您正在指定FileShare.None,根据文档,这将导致在您打开文件时任何其他尝试打开文件失败。由于您(和您的观察者)所做的只是阅读,因此请尝试使用FileShare.Read

于 2009-05-16T13:01:01.127 回答
2

简单的解决方案是在收到通知后处理 filesystemwatcher。在复制文件之前,让当前线程等待,直到它收到 filesystemwatcher 处置事件。然后您可以继续复制更改的文件而不会出现访问问题。我有同样的要求,我完全按照我提到的那样做了。有效。

示例代码:

public void TestWatcher()
{
    using (var fileWatcher = new FileSystemWatcher())
    {

        string path = @"C:\sv";
        string file = "pos.csv";

        fileWatcher.Path = path;
        fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
        fileWatcher.Filter = file;

        System.EventHandler onDisposed = (sender,args) =>
        {
           eve.Set();
        };

        FileSystemEventHandler onFile = (sender, fileChange) =>
        {
           fileWatcher.EnableRaisingEvents = false;
           Thread t = new Thread(new ParameterizedThreadStart(CopyFile));
           t.Start(fileChange.FullPath);
           if (fileWatcher != null)
           {
               fileWatcher.Dispose();
           }
           proceed = false;
        };

        fileWatcher.Changed += onFile;
        fileWatcher.Created += onFile;
        fileWatcher.Disposed+= onDisposed;
        fileWatcher.EnableRaisingEvents = true;

        while (proceed)
        {
            if (!proceed)
            {
                break;
            }
        }
    }
}

public void CopyFile(object sourcePath)
{
    eve.WaitOne();
    var destinationFilePath = @"C:\sv\Co";
    if (!string.IsNullOrEmpty(destinationFilePath))
    {
        if (!Directory.Exists(destinationFilePath))
        {
            Directory.CreateDirectory(destinationFilePath);
        }
        destinationFilePath = Path.Combine(destinationFilePath, "pos.csv");
    }           

    File.Copy((string)sourcePath, destinationFilePath);
}
于 2011-02-11T11:07:53.893 回答
2

FileSystemWatcher 触发 watcher.Created 事件两次,每次创建文件时,文件复制开始时 1ce 和文件复制完成时第二次。您所要做的就是忽略第一个事件并第二次处理事件。

事件处理程序的一个简单示例:

private bool _fileCreated = false;
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e)
{
    if (_fileCreated)
    {
        ReadFromFile();//just an example method call to access the new file
    }

    _fileCreated = !_fileCreated;
}
于 2012-02-01T15:17:20.743 回答
1

我觉得你想要的一个很好的例子是 log4net 中的 ConfigureAndWatchHandler。他们使用计时器来触发文件处理程序事件。我觉得这最终成为 0xA3 帖子中 while 循环的更清洁实现。对于那些不想使用 dotPeek 检查文件的人,我将尝试在此处根据 OP 代码为您提供代码片段:

private System.Threading.Timer _timer;    

public void Run() {
  //setup filewatcher
  _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1);
}

private void OnFileChange(object state)
{
    try 
    {
    //handle files
    }
    catch (Exception ex) 
    {
        //log exception
        _timer.Change(500, -1);
    }
}
于 2011-06-14T17:18:59.040 回答
1

我有类似的问题。只是因为 FileSystemWatcher。我刚刚使用了
Thread.Sleep();

现在它工作正常。当文件进入目录时,它会调用 onCreated 两次。所以当文件被复制时一次。第二次复制完成时。为此,我使用了 Thread.Sleep(); 所以它会在我调用 ReadFile(); 之前等待。

private static void OnCreated(object source, FileSystemEventArgs e)
    {
        try
        {
            Thread.Sleep(5000);
            var data = new FileData();
            data.ReadFile(e.FullPath);                
        }
        catch (Exception ex)
        {
            WriteLogforError(ex.Message, String.Empty, filepath);
        }
    }
于 2016-08-08T09:19:34.250 回答
0

我在 DFS 中遇到了同样的问题。我的解决方案是通过在每个文件中添加两个空行来实现的。然后我的代码等待文件中的两个空行。然后我确定从文件中读取整个数据。

于 2009-05-16T12:46:00.273 回答
0
public static BitmapSource LoadImageNoLock(string path)
{
    while (true)
    {
        try
        {
            var memStream = new MemoryStream(File.ReadAllBytes(path));
            var img = new BitmapImage();
            img.BeginInit();
            img.StreamSource = memStream;
            img.EndInit();
            return img;
            break;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}
于 2012-07-04T08:45:27.660 回答