Ошибка доступа к файлу с помощью FileSystemWatcher, когда в каталог добавлено несколько файлов
У меня возникает проблема с 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, потому что я хочу, чтобы только один сервер разбирал файл - сервер, который попадает в файл, сначала анализирует его.
Ответы
Ответ 1
Типичная проблема такого подхода заключается в том, что файл все еще копируется во время запуска события. Очевидно, вы получите исключение, потому что файл заблокирован во время копирования. Исключение особенно вероятно для больших файлов.
В качестве обходного пути вы можете сначала скопировать файл, а затем переименовать его и прослушать событие переименования.
Или другой вариант должен состоять в том, чтобы цикл while проверял, можно ли открыть файл с доступом для записи. Если это возможно, вы узнаете, что копирование завершено. Код С# может выглядеть так (в производственной системе вы можете иметь максимальное количество попыток или тайм-аута вместо 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 будет прослушивать только файл триггера.
Ответ 2
Я бы оставил комментарий выше, но пока у меня недостаточно очков.
Самый верный ответ на этот вопрос имеет блок кода, который выглядит следующим образом:
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
, ему удалось получить доступ к файлу (потому что он запрашивал ограничение на Share, которое может быть выполнено), но вызов по нисходящему каналу продолжал сбой.
Параметр общего доступа в вызове File.Open
должен быть либо FileShare.Read
, либо FileShare.None
, а NOT FileShare.ReadWrite
.
Ответ 3
Когда вы открываете файл в методе OnChanged, вы указываете FileShare.None
, который, согласно документации, приведет к другие попытки открыть файл сбой, пока вы его открыли. Поскольку все, что вы делаете (и ваш наблюдатель), читаете, попробуйте вместо этого использовать FileShare.Read
.
Ответ 4
Простым решением было бы избавиться от файловой системы после получения уведомления. перед копированием файла сделайте текущий поток до тех пор, пока он не получит событие, связанное с файловой системой. то вы можете продолжить копирование измененного файла без проблем доступа. У меня было такое же требование, и я сделал это точно так же, как и то, что я упомянул. он работал.
Пример кода:
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);
}
Ответ 5
FileSystemWatcher запускает watcher.Created event два раза для каждого создания файла
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;
}
Ответ 6
Я считаю хорошим примером того, что вы хотите - ConfigureAndWatchHandler в log4net. Они используют таймер для запуска события обработчика файлов. Я чувствую, что это становится более чистой реализацией цикла while в сообщении 0xA3. Для тех из вас, кто не хочет использовать 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);
}
}
Ответ 7
У меня была аналогичная проблема. Это просто из-за 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);
}
}
Ответ 8
У меня была та же проблема в DFS. Мое разрешение было достигнуто путем добавления двух пустых строк в каждый файл. Затем мой код ждет две пустые строки в файле. Тогда у меня есть определенность для чтения целых данных из файла.
Ответ 9
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);
}
}
}