如何检测文件缓冲区是否已刷新

时间:2019-01-18 11:34:18

标签: c# dllimport filesystemwatcher

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

这是我的测试示例。

DataIntegrityViolationException

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

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

编辑: 更新了FileWatcherTest类

2 个答案:

答案 0 :(得分: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);

您只给磁盘10毫秒做出反应,也许这是另一个问题。

答案 1 :(得分:0)

经过一些搜索,我发现FileSystemWatcher仅在文件关闭后触发事件,如此article所示。本文仅提到了修改日期为NotifyFilter的日期,但是在我的测试中,我发现所有Notifyfilters在文件关闭后才会触发,而永远不会在文件打开时触发。

由于这个原因,看起来尾部只能通过循环功能连续监视文件中是否有多余的行来实现。我以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();
            }
        }
    }
}

如果任何人都知道无需使用定时函数即可完成拖尾的方法,我将接受它作为答案。在那之前,我认为我的答案是唯一可行的方法。