c# filesystemwatcher 监控文件是不是写完

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c# filesystemwatcher 监控文件是不是写完相关的知识,希望对你有一定的参考价值。

程序是这样的,它通过filesystemwatcher监控一个A文件夹的.jpg 文件,如果有新文件则传到服务器的B文件夹。 测试的时候把比较大的文件拷贝到A文件夹时会发生错误,因为该文件正被其他进程占用。我使用一个队列去保存这些大文件,即如果大文件进入A文件夹,则程序会发生异常,在catch块把这些发生异常的图片路径放到队列里,使用timer去定时上传这些文件。 现在发现一个新问题,A文件主要用来存放采集卡采集回来的图片,这些图片是先建立个空的jpg 文件,然后不断写入流。并且这些文件在复制时不会出现正在被其他文件占用的错误。所以在filesystemwatcher监控的时候经常把0KB的空文件上传到服务器。我已经把监控的类型设置为监控文件名或最后一次打开文件时间或属性改变或大小改变等,所有的监控类型都搭配试过了,但是还是不能解决上传0KB文件的问题。 谁能给个解决办法,感激不尽!

类型里面有个NotifyFilters.LastWrite好像什么滴,然后要用他的changed事件,千万别用created事件。很多人在网上复制一大堆的:
NotifyFilters.LastAccess | NotifyFilters.LastWrite| NotifyFilters.FileName | NotifyFilters.DirectoryName;
奇了怪了,怎么处理了两次????
你自己写的代码要自己去看,网上那么多人说是filesystemwatcher问题,看看到底是哪里问题,对于文件名的changed确实是改变了三次啊,改变了FileName的时候,肯定也改变了LastAccess,有人还发现处理了三次呢,为什么???他监控下载文件夹,下载文件是创建了,然后改名创建,LastWrite也包括了,触发changed三次!DirectoryName这个东西是没用的,只能触发一次,后面就拜拜,因为你的代码检测他啊,他改变一次,filesystemwatcher在原来的路径就找不到他了,又要重新发现文件,这时候的文件名你已经改了。
所以我上面说那么多,就会引来后面的问题,系统会溢出奔溃,其实这个控件是个异步控件,正在里面使用控件要委托,避免跨线程检查;方法使用try,catch机制会跳转,没有cacth到,就跳了;一连执行三次一样的方法去操作文件,有可能第一次把文件删除了,第二次交叉执行又去使用文件路径,你说找不到路径的System.IO类型操作是不是系统溢出,这样就会奔溃。
这个控件还有一个小问题,你要注意framework版本,是否认识NotifyFilters.LastWrite
参考技术A filesystemwatcher检测到新文件的时候不要直接上传.先建立一个timer监视文件大小.等文件大小不变了才上传应该就可以了.

C# 中的 FileStream 和 FileSystemWatcher,奇怪的问题“进程无法访问文件”

【中文标题】C# 中的 FileStream 和 FileSystemWatcher,奇怪的问题“进程无法访问文件”【英文标题】:FileStream and a FileSystemWatcher in C#, Weird Issue "process cannot access the file" 【发布时间】:2014-03-11 10:42:22 【问题描述】:

我有一个复杂的代码库,它正在侦听某个文件夹上的 FileCreated 事件。创建文件后(还包括将文件移动到该文件夹​​),我想读入该文件并对其进行处理。它适用于第一个文件,但在所有其他尝试之后抛出异常。在调试模式下(使用 VisualStudio)会抛出错误,但如果我只是单击“继续”.. 它就会工作(没有错误)。

我已经发布了简化的代码,它演示了这个问题。

比如你启动应用程序,点击“开始”按钮,然后“新建一个文本文件”

输出是:

Working

如果您随后以完全相同的方式创建第二个文件,则输出为:

Broken: The process cannot access the file 'C:\TestFolder\New Text Document (2).txt' because it is being used by another process.
Working, after breaking

查看我的代码后,您会看到上面的一组打印输出意味着首先抛出了“无法访问文件”异常,但在 catch 语句中执行相同的调用突然起作用了。

这对我来说毫无意义,因为该文件显然没有被其他任何东西使用(我刚刚创建它).. 无论如何它会在一秒钟后工作....

下面是我的代码

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" >
    <StackPanel>
        <Button Click="Button_Click"  Content="Start"/>
    </StackPanel>
</Window>

代码背后:

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;


namespace WpfApplication1
 
    public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();
        

        private void Button_Click(object sender, RoutedEventArgs e)
        
            test();
        


        String Folder = @"C:\TestFolder";

        private void test()
        
            FileSystemWatcher watch = new FileSystemWatcher(Folder);
            watch.Created += new FileSystemEventHandler(FileCreated);
            watch.EnableRaisingEvents = true;

            Process.Start(Folder);
        

        private void FileCreated(object sender, FileSystemEventArgs fsEvent)
        

            if (File.Exists(fsEvent.FullPath))
            

                // Thread.Sleep(1000);// Sleeping for 1 second seems to prevent the error from happening...?
                // If i am debugging, and pause on exceptions... then it also suddenly works (similar to the Sleep above)
                try
                

                    FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open); 
                    Console.WriteLine("Working");
                    fs.Close();
                
                catch (IOException ex)
                
                    Console.WriteLine("Broken: " + ex.Message);
                    try
                                            
                        FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
                        Console.WriteLine("Working, after breaking");
                        fs.Close();

                    
                    catch(IOException ex2)
                                            
                        FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
                        Console.WriteLine("really broken: " + ex2.Message);
                        fs.Close();
                    
                


            
        
    

【问题讨论】:

【参考方案1】:

自 .NET 1.0 以来,我已经看到了您所描述的行为,并且从未费心找出它发生的原因。似乎操作系统或 .NET 有时(?)在您调用 close 和 dispose 后会在短时间内锁定文件。

我做了一个变通办法——或者如果你愿意,也可以破解——事实证明这对我们来说非常强大。我们每天在服务器场中处理数百万个文件,文件观察器检测到的所有文件在移交给进一步处理之前都会通过此方法。

它的作用是在文件上放置一个排他锁。如果失败,它会选择等待最多 10 秒以关闭文件,然后再放弃。

    public static bool IsFileClosed(string filepath, bool wait)
    
        bool        fileClosed = false;
        int         retries = 20;
        const int   delay = 500; // Max time spent here = retries*delay milliseconds

        if (!File.Exists(filepath))
            return false;

        do
        
            try 
            
                // Attempts to open then close the file in RW mode, denying other users to place any locks.
                FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                fs.Close();
                fileClosed = true; // success
            
            catch (IOException) 

            if (!wait) break;

            retries --;

            if (!fileClosed)
                Thread.Sleep( delay );
        
        while (!fileClosed && retries > 0);

        return fileClosed;
    

【讨论】:

+1 我可以试试这个...我的简单“睡眠(1000)”现在似乎工作正常。【参考方案2】:

这里发生的很可能是FileCreated 事件正在引发并尝试在文件完全写入磁盘之前处理该文件。

请参阅Wait Until File Is Completely Written 了解避免此问题的技术。

【讨论】:

+1。但是知道为什么它适用于创建的第一个文件,而不适用于之后的其他文件吗? 不确定...答案将取决于用于创建文件的机制。【参考方案3】:

尝试禁用任何防病毒/反恶意软件。大多数配置为默认在创建时扫描文件。

【讨论】:

很好,但即使有例外规则(在生产环境中很少选择禁用),如果您在文件关闭后立即访问文件,我已经看到这些锁定。 也许,但它仍然没有解释为什么它工作一次,然后失败。 +1 无论如何都是一个有趣的想法。 (他们也不喜欢我们在工作时关闭杀毒软件:P) 近 8 年后,一位同事提到,他们认为这也是对所有新创建的文件执行的操作。没有办法证明它,也可能没有办法避免它。但只是想让你大声喊叫。 感谢您对该主题的投入 ;-) 如果您仍在为此苦苦挣扎,请检查是否有任何东西正在复制文件系统,或者底层存储是否有网络支持【参考方案4】:

Ty to EventHorizo​​n 获取上面的代码,非常适合我的需求,并且似乎抓住了 Brendan 分享的链接中的精髓。

将其稍微修改为返回元组的异步(既用于在 do/while 结束时取回文件锁定状态,也用于让 ms 传递以查看发生了哪种退出)。唯一让我不舒服的事情,我不确定为什么会这样,即使将延迟降低到 2ms 我仍然无法触发 catch IOException 条件(多个循环的证据) 所以我对 IO 故障案例没有任何直接可见性,其他人可能有更大的文件,他们可以通过以下方式验证这一点:

public async Task<(bool, int)> IsFileClosed(string filepath)

  bool fileClosed = false;
  int baseretries = 40;
  int retries = 40;
  const int delay = 250;

  if (!File.Exists(filepath))
    return (false,0);

  Task<bool> FileCheck = Task.Run(() =>
  
    do
    
      try
      
        FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        fs.Close();
        fileClosed = true;
      
      catch (IOException)  
      retries--;

      if (!fileClosed)
        Thread.Sleep(delay);
    
    while (!fileClosed && retries > 0);
    return fileClosed;
  );

  fileClosed = await FileCheck;
  return (fileClosed, (baseretries - retries) * delay);

【讨论】:

以上是关于c# filesystemwatcher 监控文件是不是写完的主要内容,如果未能解决你的问题,请参考以下文章

c# filesystemwatcher 监控文件是不是写完

C#的FileSystemWatcher用法实例详解

C# 监控指定目录的文件变化

C#怎监控例似WORD这样用临时文档记录修改过的文件,并复制修改后的文件?

C#(098):文件监视 FileSystemWatcher

使用filesystemwatcher时怎么处理大文件