我有这个复杂的代码库,它正在侦听某个文件夹上的FileCreated事件。当文件被创建(其中还包括将文件移动到该文件夹)时,我想读取该文件并对其执行某些操作。它适用于第一个文件,但在所有其他尝试之后抛出异常。在调试模式下(使用VisualStudio),将抛出错误,但如果我只是单击"继续" ..然后它将起作用(没有错误)。
我发布了简化代码,证明了这个问题。
例如,您启动应用程序,单击"开始"按钮,然后"创建一个新的文本文件"
输出结果为:
Working
如果您以完全相同的方式创建2ed文件,则输出为:
Broken: The process cannot access the file 'C:\TestFolder\New Text Document (2).txt' because it is being used by another process.
Working, after breaking
查看我的代码后,您会看到上面的打印输出意味着首先有一个"无法访问该文件"抛出异常,但在catch语句中执行相同的调用突然有效。
这对我来说没有任何意义,因为该文件显然没有被其他任何东西使用(我刚刚创建它)..无论如何它会在一秒后工作....
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" >
<StackPanel>
<Button Click="Button_Click" Content="Start"/>
</StackPanel>
</Window>
代码背后:
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
test();
}
String Folder = @"C:\TestFolder";
private void test()
{
FileSystemWatcher watch = new FileSystemWatcher(Folder);
watch.Created += new FileSystemEventHandler(FileCreated);
watch.EnableRaisingEvents = true;
Process.Start(Folder);
}
private void FileCreated(object sender, FileSystemEventArgs fsEvent)
{
if (File.Exists(fsEvent.FullPath))
{
// Thread.Sleep(1000);// Sleeping for 1 second seems to prevent the error from happening...?
// If i am debugging, and pause on exceptions... then it also suddenly works (similar to the Sleep above)
try
{
FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
Console.WriteLine("Working");
fs.Close();
}
catch (IOException ex)
{
Console.WriteLine("Broken: " + ex.Message);
try
{
FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
Console.WriteLine("Working, after breaking");
fs.Close();
}
catch(IOException ex2)
{
FileStream fs = new FileStream(fsEvent.FullPath, FileMode.Open);
Console.WriteLine("really broken: " + ex2.Message);
fs.Close();
}
}
}
}
}
}
答案 0 :(得分:8)
我已经看到了自.NET 1.0以来你所描述的行为,并且从未费心去发现它为什么会发生。在调用close和dispose之后,OS或.NET有时(?)会在短时间内锁定文件。
我做了一个解决方法 - 或者如果你喜欢黑客 - 这对我们来说已经证明非常强大。我们每天在服务器场中处理数百万个文件,并且filewatchers检测到的所有文件在传递给进一步处理之前都会通过此方法。
它的作用是对文件进行独占锁定。如果失败,它将可选择等待最多10秒钟,以便在放弃之前关闭文件。
public static bool IsFileClosed(string filepath, bool wait)
{
bool fileClosed = false;
int retries = 20;
const int delay = 500; // Max time spent here = retries*delay milliseconds
if (!File.Exists(filepath))
return false;
do
{
try
{
// Attempts to open then close the file in RW mode, denying other users to place any locks.
FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
fs.Close();
fileClosed = true; // success
}
catch (IOException) {}
if (!wait) break;
retries --;
if (!fileClosed)
Thread.Sleep( delay );
}
while (!fileClosed && retries > 0);
return fileClosed;
}
答案 1 :(得分:3)
这里发生的最有可能的事情是FileCreated
事件被引发并尝试在完全写入磁盘之前处理该文件。
有关避免此问题的技巧,请参阅Wait Until File Is Completely Written。
答案 2 :(得分:1)
尝试禁用任何防病毒/反恶意软件。大多数配置为默认情况下在创建时扫描文件。
答案 3 :(得分:0)
Ty to EventHorizon 对于上面的代码,非常适合我的需求,并且似乎抓住了 Brendan 分享的链接中的本质。
将其稍微修改为异步返回元组(用于在 do/while 结束时获取文件锁定状态并通过 ms 以查看发生了哪种退出)。我唯一不舒服的是,我不确定为什么会这样,即使将延迟降低到 2 毫秒,我仍然无法触发 catch IOException 条件(超过一个 do 循环的证据) ) 所以我没有任何关于 IO 失败案例的直接可见性,其他人可能有更大的文件,他们可以验证这一点:
public async Task<(bool, int)> IsFileClosed(string filepath)
{
bool fileClosed = false;
int baseretries = 40;
int retries = 40;
const int delay = 250;
if (!File.Exists(filepath))
return (false,0);
Task<bool> FileCheck = Task.Run(() =>
{
do
{
try
{
FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
fs.Close();
fileClosed = true;
}
catch (IOException) { }
retries--;
if (!fileClosed)
Thread.Sleep(delay);
}
while (!fileClosed && retries > 0);
return fileClosed;
});
fileClosed = await FileCheck;
return (fileClosed, (baseretries - retries) * delay);
}