当多个文件放入监视目录时,我遇到了FileSystemWatcher的问题。我想在文件放入目录后立即解析它。通常,第一个文件解析正常,但向目录添加第二个文件会导致访问问题。偶尔,第一个文件甚至不会解析。只有一个应用程序正在运行并正在查看此目录。最终,此进程将在多台计算机上运行,并且它们将监视共享目录,但只有一台服务器可以解析每个文件,因为数据已导入数据库且没有主键。
这是FileSystemWatcher代码:
public void Run() {
FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp");
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Filter = "*.txt";
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}
然后解析文件的方法:
private void OnChanged(object source, FileSystemEventArgs e) {
string line = null;
try {
using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) {
using (StreamReader sr = new StreamReader(fs)) {
while (sr.EndOfStream == false) {
line = sr.ReadLine();
//parse the line and insert into the database
}
}
}
}
catch (IOException ioe) {
Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString());
}
移动第二个文件时,它正在捕捉
System.IO.IOException:进程无法访问文件'C:\ Temp \ TestFile.txt',因为它正由另一个进程使用。
如果它在多台计算机上运行,我希望看到这个错误,但它现在只在一台服务器上运行。不应该有另一个使用此文件的进程 - 我创建了它们并在应用程序运行时将它们复制到目录中。
这是设置FileSystemWatcher的正确方法吗?如何查看此文件的锁定?为什么不解析这两个文件 - 我是否必须关闭FileStream?我想保留FileShare.None选项,因为我只想要一个服务器来解析文件 - 获取文件的服务器首先解析它。
答案 0 :(得分:52)
此方法的典型问题是在触发事件时仍在复制文件。显然,您将获得异常,因为文件在复制期间被锁定。大文件特别容易出现异常。
作为一种解决方法,您可以先复制该文件,然后重命名该文件并收听重命名事件。
或者另一种选择是使用while循环检查是否可以使用写访问权打开文件。如果可以,您将知道复制已完成。 C#代码可能如下所示(在生产系统中,您可能希望具有最大重试次数或超时而不是while(true)
):
/// <summary>
/// Waits until a file can be opened with write permission
/// </summary>
public static void WaitReady(string fileName)
{
while (true)
{
try
{
using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
if (stream != null)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
break;
}
}
}
catch (FileNotFoundException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
catch (IOException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
catch (UnauthorizedAccessException ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
}
Thread.Sleep(500);
}
}
另一种方法是在复制完成后在文件夹中放置一个小的触发器文件。您的FileSystemWatcher只会侦听触发器文件。
答案 1 :(得分:9)
我上面留下了评论,但我还没有足够的分数。
这个问题的最高评价答案有一段代码如下:
using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
if (stream != null)
{
System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
break;
}
}
使用FileShare.ReadWrite
设置的问题在于它正在请求访问该文件,主要是说“我想读/写这个文件,但其他人也可以读/写它”。这种方法在我们的情况下失败了接收远程传输的进程没有锁定文件,但它正在积极地写入它。我们的下游代码(SharpZipLib)因“正在使用文件”异常而失败,因为它试图用FileShare.Read
打开文件(“我想要读取文件,只允许其他进程读取”) 。由于打开文件的进程已经写入,因此该请求失败。
但是,上面回复中的代码太宽松了。通过使用FileShare.ReadWrite
,它成功获得了对文件的访问权限(因为它要求可以兑现的共享限制),但下游调用仍然失败。
File.Open
来电中的分享设置应为FileShare.Read
或FileShare.None
,不 FileShare.ReadWrite
。
答案 2 :(得分:4)
当您在OnChanged方法中打开文件时,您指定FileShare.None
,根据the documentation,将导致任何其他尝试打开文件失败而您打开它。由于您(和您的观察者)正在阅读,请尝试使用FileShare.Read
。
答案 3 :(得分:2)
简单的解决方案是在收到通知后处理filesystemwatcher。在复制文件之前,让当前线程等待,直到它收到filesystemwatcher处理的事件。然后您可以继续复制更改的文件而不会出现访问问题。我有同样的要求,我做的完全像我提到的那样。它奏效了。
示例代码:
public void TestWatcher()
{
using (var fileWatcher = new FileSystemWatcher())
{
string path = @"C:\sv";
string file = "pos.csv";
fileWatcher.Path = path;
fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
fileWatcher.Filter = file;
System.EventHandler onDisposed = (sender,args) =>
{
eve.Set();
};
FileSystemEventHandler onFile = (sender, fileChange) =>
{
fileWatcher.EnableRaisingEvents = false;
Thread t = new Thread(new ParameterizedThreadStart(CopyFile));
t.Start(fileChange.FullPath);
if (fileWatcher != null)
{
fileWatcher.Dispose();
}
proceed = false;
};
fileWatcher.Changed += onFile;
fileWatcher.Created += onFile;
fileWatcher.Disposed+= onDisposed;
fileWatcher.EnableRaisingEvents = true;
while (proceed)
{
if (!proceed)
{
break;
}
}
}
}
public void CopyFile(object sourcePath)
{
eve.WaitOne();
var destinationFilePath = @"C:\sv\Co";
if (!string.IsNullOrEmpty(destinationFilePath))
{
if (!Directory.Exists(destinationFilePath))
{
Directory.CreateDirectory(destinationFilePath);
}
destinationFilePath = Path.Combine(destinationFilePath, "pos.csv");
}
File.Copy((string)sourcePath, destinationFilePath);
}
答案 4 :(得分:2)
对于每个单独的文件创建,FileSystemWatcher都会触发watcher.Created事件两次 启动文件复制时为1ce,文件复制完成时为第2次。您所要做的就是第二次忽略第一个事件和处理事件。
事件处理程序的一个简单示例:
private bool _fileCreated = false;
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e)
{
if (_fileCreated)
{
ReadFromFile();//just an example method call to access the new file
}
_fileCreated = !_fileCreated;
}
答案 5 :(得分:1)
我觉得你想要的一个很好的例子是log4net中的ConfigureAndWatchHandler。他们使用计时器来触发文件处理程序事件。我觉得这最终成为0xA3帖子中while循环的一个更清晰的实现。对于那些不想使用dotPeek来检查文件的人,我会尝试根据OP代码给你一个代码片段:
private System.Threading.Timer _timer;
public void Run() {
//setup filewatcher
_timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1);
}
private void OnFileChange(object state)
{
try
{
//handle files
}
catch (Exception ex)
{
//log exception
_timer.Change(500, -1);
}
}
答案 6 :(得分:1)
我有类似的问题。它只是因为FileSystemWatcher。我刚刚用过 Thread.sleep代码();
现在工作正常。 当文件进入目录时,它会调用onCreated两次。一旦文件被复制,第二次复制完成。为此,我使用了Thread.Sleep();所以它会在我调用ReadFile();
之前等待private static void OnCreated(object source, FileSystemEventArgs e)
{
try
{
Thread.Sleep(5000);
var data = new FileData();
data.ReadFile(e.FullPath);
}
catch (Exception ex)
{
WriteLogforError(ex.Message, String.Empty, filepath);
}
}
答案 7 :(得分:0)
我在DFS中遇到了同样的问题。通过向每个文件添加两个空行来实现我的解决方案。然后我的代码等待文件中的两个空行。然后我确定从文件中读取整个数据。
答案 8 :(得分:0)
public static BitmapSource LoadImageNoLock(string path)
{
while (true)
{
try
{
var memStream = new MemoryStream(File.ReadAllBytes(path));
var img = new BitmapImage();
img.BeginInit();
img.StreamSource = memStream;
img.EndInit();
return img;
break;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}