如何检测文件缓冲区是否已被刷新
How to detect if a File buffer has been flushed
我正在尝试创建一个简单的日志记录工具来监视文件更改。我已经使用 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 class
不幸的是Flush
没有刷新你想要的东西。我找了很多文章来解释它,例如:
从.net 4开始就有解决方法,使用FileStream
的另一种重载方法:Flush(bool)
var fs = writer.BaseStream as FileStream;
fs.Flush(true);
而且你只给磁盘 10ms 的反应时间,也许这是另一个问题。
经过一番搜索,我发现 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();
}
}
}
}
如果有人知道不使用定时函数就可以完成拖尾的方法,我会接受它作为答案。直到那个时候,我觉得我的回答是唯一可行的方法。
我正在尝试创建一个简单的日志记录工具来监视文件更改。我已经使用 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 class
不幸的是Flush
没有刷新你想要的东西。我找了很多文章来解释它,例如:
从.net 4开始就有解决方法,使用FileStream
的另一种重载方法:Flush(bool)
var fs = writer.BaseStream as FileStream;
fs.Flush(true);
而且你只给磁盘 10ms 的反应时间,也许这是另一个问题。
经过一番搜索,我发现 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();
}
}
}
}
如果有人知道不使用定时函数就可以完成拖尾的方法,我会接受它作为答案。直到那个时候,我觉得我的回答是唯一可行的方法。