System.IO.FileSystemWatcher的坑

System · 浏览次数 : 1085

小编点评

**代码描述:** 该代码监控文件夹的变化,并在文件创建时触发 `_fileSystemWatcher_Created` 事件。在 `_fileSystemWatcher_Created` 中,使用排他性的文件打开操作 `File.Open()` 来访问文件,但 `File.Open()` 函数只有在文件已成功打开时才会返回文件对象。如果传输延迟太长,文件可能会永远无法读取。 **坑点:** 1. **传输延迟问题:**FileSystemWatcher 只在文件创建时触发 `Created` 事件,这意味着如果文件传输非常缓慢,创建事件可能会被触发多次,导致循环。 2. **文件打开操作冲突:** `File.Open()` 函数只有在文件已成功打开时才会返回文件对象。如果文件在传输过程中被其他程序打开,该方法可能会抛出异常。 3. **文件最后修改时间检查问题:**当文件最后修改时间相同时,可能无法保证文件已完全被读取。 **解决方案:** 1. 使用 ** nač列模式** 打开文件,避免频繁打开和关闭文件。 2. 使用 **异步操作** 访问文件,确保文件已完全读取。 3. 设置 **文件超时时间** 来防止无限循环。 4. 使用 **排他性的文件打开操作** 来解决文件打开冲突问题。 5. 在 `_fileSystemWatcher_Created` 中,检查最后修改时间,并根据时间进行处理。 6. 在 `_fileSystemWatcher_Created` 中,使用条件判断是否文件已读取,避免无限循环。 **注意:** * 代码中的 `5` 是一个可调节的常数,可以根据实际情况进行调整。 * `Task.Delay()` 方法用于模拟文件传输过程中的延迟。 * `File.GetLastWriteTimeUtc()` 方法用于获取文件最后修改时间的 UTC 时间。

正文

System.IO命名空间下面有一个FileSystemWatcher,这个东西可以实现文件变动的提醒。需要监控文件夹变化(比如FTP服务器)的情形非常适用。

需要监控文件新建时,我们可以这么写:

_fileSystemWatcher.Path = path;
_fileSystemWatcher.IncludeSubdirectories = true;
_fileSystemWatcher.Created += _fileSystemWatcher_Created;
_fileSystemWatcher.EnableRaisingEvents = true;

protected async void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine(e.FullPath);
}

感觉还是挺方便的吧?接下来就是坑了。

传输延迟问题

FileSystemWatcher只要发现文件创建就触发了,大文件或者FTP等需要一段时间才能完成传输的情况下,直接在时间处理程序中处理文件会由于文件不完整导致错误。可惜的是,FileSystemWatcher并没有內建任何机制可以保障文件传输完成再触发Created事件,我们只能靠自己代码保障。

以下代码运行于.NET 6,Windows 11,Rocky Linux 9

Windows only方案

  • FileSystemWatcher除了Created,还提供了Changed事件,我们可以先监听Created事件,然后再监控Changed的情况,当文件属性不在变化时,认为是传输完毕了。
    这种方案可行,不过感觉有点太麻烦了,我需要监听两个事件,还需要处理先后顺序,其实我只想知道创建而已...

  • 在Created事件中,使用排他性的文件打开操作
    在File.Open()函数中,有重载可以提供独占的访问,访问不成功,文件会弹出错误。

            //防止文件上传时间过长,导致无法正常识别
            if (!File.Exists(e.FullPath)) return;
            var accessable = false;
            for (int i = 0; i < 5; i++)
            {
                try
                {
                    using (File.Open(e.FullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                    {
                        Console.WriteLine("Break");
                        accessable = true;
                        break;
                    }
                }
                catch (Exception)
                {
                    Console.WriteLine("Loop" + i);
                }
                await Task.Delay(3000);
            }
            //文件超时无法读取,失败。
            if (!accessable) return;
//后续代码

运行可以看见这样的输出,说明方案可行。

Linux与Windows通用方案

上面的方案似乎已经解决了我们的问题,我兴致勃勃地部署到Linux机器上时却死活无法正常工作,Debug发现Open()这个方法居然可以一次直接通过,看来Linux下的Share不能正常独占这个文件,还得换一个方法。

protected async void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        {
            //防止文件上传时间过长,导致无法正常识别
            if (!File.Exists(e.FullPath)) return;
            var accessable = false;
            for (int i = 0; i < 5; i++)
            {
                await Task.Delay(3000);
                Console.WriteLine("loop" + i);
                var time1 = File.GetLastWriteTimeUtc(e.FullPath);
                await Task.Delay(1000);
                var time2 = File.GetLastWriteTimeUtc(e.FullPath);
                if (time1 == time2)
                {
                    accessable = true;
                    break;
                }
            }
            //文件超时无法读取,失败。
            if (!accessable) return;
//后续代码
}

我们可以在程序中定时检查文件的最后修改时间,如果相隔一段时间的两次最后修改时间一致的话,那说明文件已经完成了传输,这种方式不依赖于打开操作,并且可以在Windows和Linux下运行。

为了防止无限循环,设置了超时,如果在指定的时间内无法完成,那么程序直接跳出。

参考

与System.IO.FileSystemWatcher的坑相似的内容:

System.IO.FileSystemWatcher的坑

System.IO命名空间下面有一个FileSystemWatcher,这个东西可以实现文件变动的提醒。需要监控文件夹变化(比如FTP服务器)的情形非常适用。 需要监控文件新建时,我们可以这么写: _fileSystemWatcher.Path = path; _fileSystemWatcher.

.NET使用原生方法实现文件压缩和解压

前言 在.NET中实现文件或文件目录压缩和解压可以通过多种方式来完成,包括使用原生方法(System.IO.Compression命名空间中的类)和第三方库(如:SharpZipLib、SharpCompress、K4os.Compression.LZ4等)。本文我们主要讲的是如何使用.NET原生方

[转帖]Linux Storage Stack Diagram - Linux I/O系统

https://www.cnblogs.com/xuyaowen/p/linux-io-system.html 今天看到一篇文章,其中有几张图很有意思,进行记录一下,我相信如果你对IO子系统有初步了解的话,将会有一些收获: Linux 存储栈:涉及比较全面,分为文件系统层,块层,设备层三层; 对上图

[转帖]018 磁盘 IO 性能监控 / 压测工具 (sar、iotop、fio、iostat)

https://my.oschina.net/u/3113381/blog/5465063 1 sar 命令查看当前磁盘 IO 读写 sar(System Activity Reporter 系统活动情况报告)是 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:

[转帖][搞点翻译]系统设计基础: 缓存(Caching)

https://zhuanlan.zhihu.com/p/569553681 最近在看一些系统设计相关的知识, 按我自己的理解做些翻译和整理, 和原文不一样, 有问题欢迎指出, 细节以原文为主: https://www.educative.io/courses/grokking-the-system

[转帖]如何监测 Linux 的磁盘 I/O 性能

https://bbs.huaweicloud.com/blogs/379242 在我之前的文章:《探讨 Linux 的磁盘 I/O》中,我谈到了 Linux 磁盘 I/O 的工作原理,我们了解到 Linux 存储系统 I/O 栈由文件系统层(file system layer)、通用块层( gen

[转帖]Linux查看硬件信息超强命令sar,以及可视化工具ksar

https://juejin.cn/post/6947470401135968286 一、概述 sar(System Activity Reporter,系统活动情况报告)是Linux下系统运行状态统计工具,可从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁盘I/O、CPU

[转帖]linux性能优化-CPU利用率

参数说明 /proc/stat提供系统的CPU和任务统计信息。user(us): 用户态CPU时间,不包括下面的nice时间,但包括了guest时间。nice(ni): 代表低优先级用户态CPU时间。system(sys): 内核态CPU时间。idle(id): 空闲时间,它不包括等待I/O的时间。

Python:对程序做性能分析及计时统计

如果只是想简单地对整个程序做计算统计,通常使用UNIX下的time命令就足够了。由于我用的是Mac系统,和Linux系统的输出可能有不同,不过关键都是这三个时间:user: 运行用户态代码所花费的时间,也即CPU实际用于执行该进程的时间,其他进程和进程阻塞的时间不计入此数字;system: 在内核中执行系统调用(如I/O调用)所花费的CPU时间。total(Linux下应该是real):即挂钟时间

在System身份运行的.NET程序中以指定的用户身份启动可交互式进程

今天在技术群里,石头哥向大家提了个问题:"如何在一个以System身份运行的.NET程序(Windows Services)中,以其它活动的用户身份启动可交互式进程(桌面应用程序、控制台程序、等带有UI和交互式体验的程序)"? 我以前有过类似的需求,是在GitLab流水线中运行带有UI的自动化测试程