Зачекайте, поки файл буде повністю написаний


77

Коли файл створюється ( FileSystemWatcher_Created) в одному каталозі, я копіюю його в інший. Але коли я створити великий файл (> 10MB) він не копіювати файл, так як він починає копіювати вже, коли файл ще не закінчив створювати ...
Це призводить до того , не вдається скопіювати файл, так як він використовується іншим процесом , щоб бути піднятий. ; (
Будь-яка допомога?

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();
    }
}

1
Я знайшов тут відповідь, вибачте [цю проблему вирішено на StackOverFlow] [1] [1]: stackoverflow.com/questions/1406808/…
levi

Відповіді:


44

Існує лише вирішення проблеми, з якою ви стикаєтесь.

Перед початком процесу копіювання перевірте, чи ідентифікується файл. Ви можете викликати наступну функцію, поки не отримаєте значення 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;
}

2-й спосіб:

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;
}

2
І ви забули скопіювати значення констант ( ERROR_SHARING_VIOLATION= 32, ERROR_LOCK_VIOLATION= 33)
Жюльєн Н

@RomilKumarJain Чи можемо ми побачити використання цього методу?
Roxy'Pro

@ Roxy'Pro Що ви маєте на увазі під використанням?
Роміль Кумар Джайн

15

З документації на FileSystemWatcher:

OnCreatedПодія виникає , як тільки створюється файл. Якщо файл копіюється або переноситься в переглянутий каталог, OnCreatedподія негайно буде піднята, а потім одна або кілька OnChangedподій.

Отже, якщо копія не вдається, (упіймайте виняток), додайте її до списку файлів, які все ще потрібно перемістити, і спробуйте скопіювати під час 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 {}
     }

}


це досить хороший спосіб, але мені це потрібно відразу, тому я вклав IsFileReady в той час, і він працює зараз
levi

11

Це стара тема, але я додаю трохи інформації для інших людей.

Я зіткнувся з подібною проблемою з програмою, яка пише файли PDF, іноді їх візуалізація займає 30 секунд .. це той самий період, що очікує мій клас watcher_FileCreated перед копіюванням файлу.

Файли не були заблоковані.

У цьому випадку я перевірив розмір PDF-файлу, а потім зачекав 2 секунди, перш ніж порівнювати новий розмір, якщо вони були нерівними, потік заснув би 30 секунд і спробував би знову.


5

Вам насправді пощастило - програма, яка пише файл, блокує його, тому ви не можете відкрити його. Якби він не заблокував його, ви скопіювали б частковий файл, не маючи уявлення, що проблема.

Коли ви не можете отримати доступ до файлу, ви можете припустити, що він все ще використовується (ще краще - спробуйте відкрити його в ексклюзивному режимі і подивіться, чи відкриває його хтось інший, замість того, щоб здогадуватися про несправність File.Copy). Якщо файл заблоковано, вам доведеться скопіювати його в інший час. Якщо він не заблокований, ви можете скопіювати його (тут існує невелика можливість для перегонових умов).

Коли це "інший час"? Я не пам’ятаю, коли FileSystemWatcher надсилає кілька подій на файл - перевірте це, можливо, вам буде достатньо просто проігнорувати подію та почекати на іншу. Якщо ні, ви завжди можете встановити час і перевірити файл за 5 секунд.


3

Ну ви вже самі даєте відповідь; вам потрібно почекати, поки створення файлу закінчиться. Один із способів зробити це - перевірити, чи файл все ще використовується. Приклад цього можна знайти тут: Чи є спосіб перевірити, чи використовується файл?

Зверніть увагу, що вам доведеться змінити цей код, щоб він працював у вашій ситуації. Ви можете мати щось на зразок (псевдокоду):

public static void listener_Created()
{
   while CheckFileInUse()
      wait 1000 milliseconds

   CopyFile()
}

Очевидно, що ви повинні захистити себе від нескінченного whileна той випадок, якщо додаток-власник ніколи не відпустить замок. Крім того, можливо, варто перевірити інші події, на які FileSystemWatcherви можете підписатися. Може бути подія, яку ви можете використати, щоб обійти всю цю проблему.


2

Коли файл пишеться в двійковій формі (байт за байтом), створіть FileStream та вищевказані рішення. щоб розпочати обробку у файлі

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!

2

Отже, швидко оглянувши деякі з цих та інших подібних питань, сьогодні вдень я вирушив у веселу погоню за гусями, намагаючись вирішити проблему з двома окремими програмами, використовуючи файл як метод синхронізації (а також збереження файлів). Трохи незвична ситуація, але вона, безумовно, висвітлила для мене проблеми з підходом "перевірити, чи файл заблоковано, а потім відкрити його, якщо ні".

Проблема полягає в наступному: файл може стати замкнений між часом , що ви перевірити його і час , яке ви на самому справі відкрити файл. Дійсно важко відстежити спорадичні Не вдається скопіювати файл, оскільки він використовується іншим процесом помилкою якщо ви його теж не шукаєте.

Основна роздільна здатність - просто спробувати відкрити файл всередині блоку 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's 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;
    }
}

Ще одна невеличка примітка щодо парелелізму: Це синхронний метод, який блокує його потік як під час очікування, так і під час роботи над потоком. Це найпростіший підхід, але якщо файл тривалий час залишається заблокованим, програма може не реагувати. Парелелізм - це занадто велика тема, щоб його можна було глибше заглибитись (і кількість способів, якими ви можете налаштувати асинхронне читання / запис, є якось недоречною), але тут є один із способів його усунення.

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!

0

Ви можете використовувати наступний код, щоб перевірити, чи файл можна відкрити з ексклюзивним доступом (тобто його не відкриває інша програма). Якщо файл не закрито, можна почекати кілька хвилин і перевірити ще раз, поки файл не буде закритий, і ви зможете безпечно скопіювати його.

Ви все одно повинні перевірити, чи не вдається 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;
    }
}

Що повертає цей код, якщо ви зіткнетеся з винятком, який не є винятком IOE?
Вільям Даніель

Якщо File.Open видає виняток, відмінний від IOException, цей метод не повертає значення. Абонент несе відповідальність за перевірку будь-яких винятків, не пов’язаних з IOE, які можуть бути використані: msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
Майкл

-5

Я хотів би додати тут відповідь, тому що це спрацювало для мене. Я використовував затримки часу, тоді як цикли, все, що міг подумати.

У мене було відкрито вікно Провідника Windows у вихідній папці. Я закрив його, і все працювало як шарм.

Сподіваюся, це комусь допомагає.


4
отже, тепер ваш додаток аварійно завершує роботу, якщо у користувача відкрито цей каталог?
Epirocks
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.