【问题标题】:How to detect if a File buffer has been flushed如何检测文件缓冲区是否已被刷新
【发布时间】:2019-01-18 11:34:17
【问题描述】:

我正在尝试创建一个简单的日志记录工具来监控文件更改。我使用 FileSystemWatcher 来检测文件的更改,但我发现事件仅在文件关闭时触发,而不是在刷新缓冲区时触发。这意味着如果在文件关闭之前添加了多行,我只会在文件关闭时看到。

这是我的测试示例。

[TestClass]
public class FileWriteTests
{

    [TestMethod]
    public void TestMethodAfterClose()
    {
        var currentDir = Environment.CurrentDirectory;
        var fileToMonitor = "test.txt";
        List<string> output = new List<string>();
        var watcherTest = new FileWatcherTest(fileToMonitor, currentDir, output);

        File.Delete(Path.Combine(currentDir, fileToMonitor));
        using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
        {
            writer.WriteLine($"test");
            writer.Flush();
        }
        System.Threading.Thread.Sleep(10);
        Assert.AreEqual(1, output.Count);
        Assert.AreEqual("test", output[0]);

    }

    [TestMethod]
    public void TestMethodAfterFlush()
    {
        var currentDir = Environment.CurrentDirectory;
        var fileToMonitor = "test.txt";
        List<string> output = new List<string>();
        var watcherTest = new FileWatcherTest(fileToMonitor, currentDir, output);

        File.Delete(Path.Combine(currentDir, fileToMonitor));

        using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
        {
            try
            {
                writer.WriteLine($"test");
                writer.Flush();
                System.Threading.Thread.Sleep(1000);
                // add break point here for BareTail
                Assert.AreEqual(1, output.Count);
                Assert.AreEqual("test", output[0]);
            }
            catch
            {
                Assert.Fail("Test failed");
            }
        }
    }

    public class FileWatcherTest
    {
        public string FileName { get; set; }
        public string Directory { get; set; }
        private List<string> linesRead;
        private FileSystemWatcher watcher;
        public FileWatcherTest(string fileName, string directory, List<string> output)
        {
            FileName = fileName;
            Directory = directory;
            linesRead = output;
            watcher = new FileSystemWatcher();
            watcher.Path = directory;
            watcher.Filter = FileName;
            watcher.Changed += Watcher_Changed;
            watcher.EnableRaisingEvents = true;
            watcher.NotifyFilter =  NotifyFilters.Attributes |
                                    NotifyFilters.CreationTime |
                                    NotifyFilters.DirectoryName |
                                    NotifyFilters.FileName |
                                    NotifyFilters.LastAccess |
                                    NotifyFilters.LastWrite |
                                    NotifyFilters.Security |
                                    NotifyFilters.Size;
        }

        private void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            using (var fileStream = File.Open(Path.Combine(Directory, FileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete | FileShare.Inheritable))
            {
                using (var reader = new StreamReader(fileStream))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        linesRead.Add(line);
                    }
                }
            }
        }
    }
}

现在 TestMethodAfterClose 成功并且 TestMethodAfterFlush 失败。当我使用程序BareTail 并在断点处等待时,我看到它在文件关闭之前更新了显示。所以这给了我一个迹象,表明这是可能的。我不知道在 C# 中是否可行,我可能需要使用 dllimport 导入一些本机函数。问题是我不知道去哪里看

如何在不使用计时器的情况下使两个测试都成功?

编辑: 更新了 FileWatcherTest 类

【问题讨论】:

  • 你在检查异常吗?在我看来,由于共享冲突,尝试在文件观察程序中打开文件以在测试中写入时会失败。
  • 在我自己的代码中我这样做了,但是除了由失败的断言引起的异常之外,我知道没有抛出异常。我在这个问题中查找的文件共享问题:stackoverflow.com/questions/4400517/…
  • @500-InternalServerError 为写入而打开的文件在共享模式正确的情况下打开用于读取时应该不会造成任何问题。

标签: c# dllimport filesystemwatcher


【解决方案1】:

不幸的是Flush 没有刷新你想要的东西。我找了很多文章来解释它,例如:

https://blogs.msdn.microsoft.com/alejacma/2011/03/23/filesystemwatcher-class-does-not-fire-change-events-when-notifyfilters-size-is-used/

从.net 4开始就有解决方案,使用FileStream的另一种重载方法:Flush(bool)

var fs = writer.BaseStream as FileStream;
fs.Flush(true);

而你只给磁盘 10ms 的反应时间,也许这是另一个问题。

【讨论】:

  • 我不想更改测试,只更改 FileWatcherTest 类。我建议你试试baretail 程序。它对冲洗功能的反应非常好。
  • 他们有不同的目的。 FileSystemWatcher 监视目录并由操作系统通知。但是Baretail 监视特定文件,因此它可以检查文件大小或持续修改时间。由于它是封闭源代码,请查看 linux 的tail 的源代码,您会发现它使用循环和睡眠。
【解决方案2】:

经过一番搜索,我发现 FileSystemWatcher 仅在文件关闭后触发事件,如article 所示。文章只提到了NotifyFilter的修改日期,但在我的测试中我发现所有的Notifyfilters都是在文件关闭后触发的,而在它仍然打开的时候永远不会触发。

因此,看起来 tailing 文件只有通过循环函数才能实现,该函数持续监控文件中的额外行。我以link 上的代码为例。

这是我的代码工作:

[TestClass]
public class FileWriteTests
{

    [TestMethod]
    public void TestMethodAfterClose_filetailing()
    {

        var currentDir = Environment.CurrentDirectory;
        var fileToMonitor = "test.txt";
        File.Delete(Path.Combine(currentDir, fileToMonitor));
        List<string> output = new List<string>();
        using (var watcherTest = new PersonalFileTail(currentDir, fileToMonitor))
        {
            watcherTest.StartTail(delegate (string line) { output.Add(line); });
            using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
            {
                writer.WriteLine($"test");
                writer.Flush();
            }
            System.Threading.Thread.Sleep(200);
            watcherTest.StopTail();
        }
        System.Threading.Thread.Sleep(10);
        Assert.AreEqual(1, output.Count);
        Assert.AreEqual("test", output[0]);
    }

    [TestMethod]
    public void TestMethodAfterFlush_filetailing()
    {
        // initiate file
        var currentDir = Environment.CurrentDirectory;
        var fileToMonitor = "test.txt";
        File.Delete(Path.Combine(currentDir, fileToMonitor));
        FileInfo info = new FileInfo(Path.Combine(currentDir, fileToMonitor));

        List<string> output = new List<string>();
        using (var watcherTest = new PersonalFileTail(currentDir, fileToMonitor))
        {
            watcherTest.StartTail(delegate (string line) { output.Add(line); });
            using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
            {
                try
                {
                    writer.WriteLine($"test");
                    writer.Flush();
                    System.Threading.Thread.Sleep(1000);
                    Assert.AreEqual(1, output.Count);
                    Assert.AreEqual("test", output[0]);
                }
                catch
                {
                    Assert.Fail("Test failed");
                }
            }
            watcherTest.StopTail();
        }
    }

    public class PersonalFileTail : IDisposable
    {
        private string filename;
        private string directory;
        private Task fileTailTask;
        private Action<string> handleResults;
        private volatile bool runTask;
        private long lastFilePosition;
        public string FileName
        {
            get { return Path.Combine(directory, filename); }
        }
        public PersonalFileTail(string directory, string filename)
        {
            this.directory = directory;
            this.filename = filename;
            this.runTask = false;
            lastFilePosition = 0;
        }

        public void StartTail(Action<string> handleResults)
        {
            this.handleResults = handleResults;
            runTask = true;
            fileTailTask = Task.Run(() => MonitorFileTask());
        }

        public void StopTail()
        {
            runTask = false;
            fileTailTask.Wait();
        }

        public IEnumerable<string> ReadLinesFromFile()
        {
            using (StreamReader reader = new StreamReader(new FileStream(FileName,
            FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
            {
                string line = "";
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
                lastFilePosition = reader.BaseStream.Length;
            }
        }

        public void MonitorFileTask()
        {
            StreamReader reader = null;
            FileStream stream = null;
            try
            {
                using(stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                using (reader = new StreamReader(stream))
                {
                    do
                    {
                        //if the file size has increased do something
                        if (reader.BaseStream.Length > lastFilePosition)
                        {
                            //seek to the last max offset
                            reader.BaseStream.Seek(lastFilePosition, SeekOrigin.Begin);

                            //read out of the file until the EOF
                            string line = "";
                            while ((line = reader.ReadLine()) != null)
                            {
                                handleResults(line);
                            }

                            //update the last max offset
                            lastFilePosition = reader.BaseStream.Position;
                        }
                        // sleep task for 100 ms 
                        System.Threading.Thread.Sleep(100);
                    }
                    while (runTask);
                }
            }
            catch
            {
                if (reader != null)
                    reader.Dispose();
                if (stream != null)
                    stream.Dispose();
            }
        }

        public void Dispose()
        {
            if(runTask)
            {
                runTask = false;
                fileTailTask.Wait();
            }
        }
    }
}

如果有人知道可以在不使用定时函数的情况下完成拖尾的方法,我会接受它作为答案。在那之前,我觉得我的答案是唯一可能的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    • 2021-10-01
    • 1970-01-01
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多