Подождите, пока файл не будет полностью написан
Когда файл создается (FileSystemWatcher_Created
) в одном каталоге, я копирую его в другой. Но когда я создаю большой файл ( > 10 МБ), он не может скопировать файл, потому что он начинает копировать уже, когда файл еще не закончен, создавая...
Это вызывает Невозможно скопировать файл, потому что он используется другим процессом.; (
Любая помощь?
class Program
{
static void Main(string[] args)
{
string path = @"D:\levan\FolderListenerTest\ListenedFolder";
FileSystemWatcher listener;
listener = new FileSystemWatcher(path);
listener.Created += new FileSystemEventHandler(listener_Created);
listener.EnableRaisingEvents = true;
while (Console.ReadLine() != "exit") ;
}
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
File.Copy(e.FullPath, @"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
Console.Read();
}
}
Существует только обходной путь для проблемы, с которой вы сталкиваетесь.
Перед запуском процесса копирования проверьте, не запущен ли файл. Вы можете вызвать следующую функцию, пока не получите значение False.
1-й метод, скопированный непосредственно из этого ответа:
private bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
Второй метод:
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
//check that problem is not in destination file
if (File.Exists(file) == true)
{
FileStream stream = null;
try
{
stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception ex2)
{
//_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
{
return true;
}
}
finally
{
if (stream != null)
stream.Close();
}
}
return false;
}
Из документации для FileSystemWatcher
:
Событие
OnCreated
создается, как только создается файл. Если файл копируется или переносится в наблюдаемый каталог, СобытиеOnCreated
будет немедленно поднято, за которым следует один или несколькоOnChanged
событий.
Итак, если копия завершается с ошибкой (catch исключение), добавьте ее в список файлов, которые все еще нужно перенести, и попробуйте копию во время события OnChanged
. В конце концов, он должен работать.
Что-то вроде (неполное, улавливать определенные исключения, инициализировать переменные и т.д.):
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
try {
File.Copy(e.FullPath, @"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
}
catch {
_waitingForClose.Add(e.FullPath);
}
Console.Read();
}
public static void listener_Changed(object sender, FileSystemEventArgs e)
{
if (_waitingForClose.Contains(e.FullPath))
{
try {
File.Copy(...);
_waitingForClose.Remove(e.FullPath);
}
catch {}
}
}
Это старый поток, но я добавлю информацию для других людей.
У меня возникла аналогичная проблема с программой, которая записывает PDF файлы, иногда они занимают 30 секунд для рендеринга. Это тот же период, в течение которого мой класс watcher_FileCreated ждет, прежде чем копировать файл.
Файлы не были заблокированы.
В этом случае я проверил размер PDF файла, а затем ждал 2 секунды перед сравнением нового размера, если они были неравными, поток будет спать в течение 30 секунд и повторите попытку.
Вам действительно повезло - программа, записывающая файл, блокирует его, поэтому вы не можете его открыть. Если бы он не заблокировал его, вы бы скопировали частичный файл, не имея никаких проблем с этим.
Если вы не можете получить доступ к файлу, вы можете предположить, что он все еще используется (еще лучше - попробуйте открыть его в эксклюзивном режиме и посмотреть, открывается ли кто-то еще в нем, вместо того, чтобы угадывать из-за отказа файла. Копировать). Если файл заблокирован, вам придется скопировать его в другое время. Если он не заблокирован, вы можете скопировать его (здесь есть небольшой потенциал для состояния гонки).
Когда это "другое время"? Я не помню, когда FileSystemWatcher отправляет несколько событий на файл - проверьте это, вам может быть достаточно просто проигнорировать событие и ждать другого. Если нет, вы всегда можете настроить время и перепроверять файл за 5 секунд.
Хорошо, вы уже сами даете ответ; вам нужно дождаться завершения создания файла. Один из способов сделать это - проверить, сохраняется ли файл. Пример этого можно найти здесь: Есть ли способ проверить, используется ли файл?
Обратите внимание, что вам придется изменить этот код, чтобы он работал в вашей ситуации. Возможно, вы захотите иметь что-то вроде (псевдокод):
public static void listener_Created()
{
while CheckFileInUse()
wait 1000 milliseconds
CopyFile()
}
Очевидно, вы должны защитить себя от бесконечного while
, если приложение владельца никогда не выпускает блокировку. Кроме того, возможно, стоит проверить другие события из FileSystemWatcher
, на которые вы можете подписаться. Может быть событие, которое вы можете использовать, чтобы обойти эту проблему.
Итак, быстро просмотрев некоторые из этих и других подобных вопросов, я отправился на веселую гусиную погоню сегодня днем, пытаясь решить проблему с двумя отдельными программами, используя файл в качестве метода синхронизации (а также сохранения файла). Немного необычная ситуация, но это определенно выделило для меня проблемы с "проверкой, заблокирован ли файл, а затем откройте его, если он не подходит".
Проблема заключается в следующем: файл может становиться заблокирован между временем его проверки и временем, когда вы действительно открываете файл. Его действительно сложно отследить спорадический Невозможно скопировать файл, потому что он используется другим процессом, если вы его тоже не ищете.
Основное разрешение - просто попытаться открыть файл внутри блока catch, чтобы, если его заблокировать, вы можете попробовать еще раз. Таким образом, между проверкой и открытием нет времени, операционная система делает это одновременно.
В этом коде используется File.Copy, но он работает точно также с любым из статических методов класса File: File.Open, File.ReadAllText, File.WriteAllText и т.д.
/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
//don't forget to either return from the function or break out fo the while loop
break;
}
catch (IOException)
{
//you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
}
Thread.Sleep(100);
//if its a very long wait this will acumulate very small errors.
//For most things it probably fine, but if you need precision over a long time span, consider
// using some sort of timer or DateTime.Now as a better alternative
timeout -= 100;
}
}
Еще одна небольшая заметка о парелелизме: Это синхронный метод, который будет блокировать поток как во время ожидания, так и при работе над потоком. Это самый простой подход, но если файл остается заблокированным в течение длительного времени, ваша программа может перестать отвечать на запросы. Parellelism - слишком большая тема для углубленного изучения здесь (и количество способов, которые вы могли бы настроить асинхронным чтением/записью, является нелепым), но вот один из способов, которым это может быть parellelized.
public class FileEx
{
public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
doWhenDone();
break;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
{
while (timeout > 0)
{
try {
return File.ReadAllText(filePath);
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
return "";
}
public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
{
while (timeout > 0)
{
try
{
File.WriteAllText(filePath, contents);
return;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
}
И вот как это можно использовать:
public static void Main()
{
test_FileEx();
Console.WriteLine("Me First!");
}
public static async void test_FileEx()
{
await Task.Delay(1);
//you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
//As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
//until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
CopyWaitAsync("file1.txt", "file1.bat", 1000);
//this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
await CopyWaitAsync("file1.txt", "file1.readme", 1000);
Console.WriteLine("file1.txt copied to file1.readme");
//The following line doesn't cause a compiler error, but it doesn't make any sense either.
ReadAllTextWaitAsync("file1.readme", 1000);
//To get the return value of the function, you have to use this function with the await keyword
string text = await ReadAllTextWaitAsync("file1.readme", 1000);
Console.WriteLine("file1.readme says: " + text);
}
//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!
Вы можете использовать следующий код, чтобы проверить, может ли файл быть открыт с эксклюзивным доступом (то есть он не открывается другим приложением). Если файл не закрыт, вы можете подождать несколько секунд и снова проверить, пока файл не будет закрыт, и вы можете спокойно его скопировать.
Вы все равно должны проверить, не сработал ли File.Copy, потому что другое приложение может открыть файл между моментом проверки файла и момента его копирования.
public static bool IsFileClosed(string filename)
{
try
{
using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
Когда файл записывается в двоичном формате (байт за байтом), создайте FileStream и выше решения Не работает, потому что файл готов и wrotted в каждом байте, поэтому в этой ситуации вам понадобится другое обходное решение: Сделайте это, когда файл создан или вы хотите начать обработку в файле
long fileSize = 0;
currentFile = new FileInfo(path);
while (fileSize < currentFile.Length)//check size is stable or increased
{
fileSize = currentFile.Length;//get current size
System.Threading.Thread.Sleep(500);//wait a moment for processing copy
currentFile.Refresh();//refresh length value
}
//Now file is ready for any process!
Я хотел бы добавить здесь ответ, потому что это сработало для меня. Я использовал задержки времени, в то время как циклы, все, что я мог придумать.
У меня появилось окно Windows Explorer открытой папки вывода. Я закрыл его, и все работало как прелесть.
Я надеюсь, что это поможет кому-то.
Посмотрите другие вопросы по меткам c# file filesystems io copy или Задайте вопрос