Почему я вижу исключение ввода-вывода с использованием File.Copy (), которое, похоже, возникает при чтении файла?

Предисловие: Я новичок в программировании и в основном самоучка.

Я пишу небольшое приложение, которое автоматически копирует файлы сохранений моей игры в каталог резервных копий. Он использует класс FileSystemWatcher для наблюдения за каталогом сохранения игры и поиска изменений, пока я играю, и вызывает OnChanged, если размер файла изменяется или в него выполняется запись.

Проблема, с которой я столкнулся, заключается в том, что когда моя программа копирует файл, она иногда дает сбой ИГРЫ (в данном случае Terraria). Я предполагаю, что это как-то связано с ограничениями доступа, с которыми у меня нет большого опыта. Я удивлен, что File.Copy () может вызвать проблемы, поскольку он просто читает, но это постоянный сбой с Terraria.

Пошаговое выполнение вызовов OnChanged показало, что Terraria записывает временные файлы, затем копирует их в фактические файлы сохранения и удаляет временные файлы. Довольно часто.

Итак, вот мой код с неуклюжим обходным решением, которое у меня есть. Он использует два таймера: _delayTimer, который запускается при первом вызове OnChanged, и _canBackupTimer, когда истекает _delayTimer. У меня не было проблем с играми, в которых я его тестировал.

Для каждого таймера интервал составляет 5 секунд, а AutoReset = True, поэтому он останавливается по истечении одного раза.

Это единственный способ избежать исключений ввода-вывода при отслеживании игры? Должен быть способ получше, но я не знаю, где искать. Я удивлен, что File.Copy ограничивает доступ к процессу сохранения игры.

Стоит ли смотреть права доступа к файлам?

    private static void _delayTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        _canBackupTimer.Enabled = true;
        _canBackupTimer.Start();
        _delayTimer.Enabled = false;
    }

    private static void _canBackupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        _lastAutoBackupTime = DateTime.Now;
        _canBackupTimer.Enabled = false;
    }


    private static void OnChanged(object source, FileSystemEventArgs e) {
        while (true) {

            if (!_canBackupTimer.Enabled && !_delayTimer.Enabled && (DateTime.Now - _lastAutoBackupTime).Seconds > 10) {
                //If neither timer is running and 10 seconds 
                //have elapsed since canBackupTimer stopped

                _delayTimer.Enabled = true;
                _delayTimer.Start();
                continue;
            }
            if (_canBackupTimer.Enabled) {
                //if canBackupTimer is running, do autobackup

                Game autoBackupGame = null;

                //_gamesToAutoBackup is a List<Game> for the program to watch.
                //Check to identify which Game is being backed up
                //g.RootFolder is the game's base directory, e.g. "...\Terraria\"
                foreach (var g in _gamesToAutoBackup) { 
                    if (e.FullPath.Contains(g.Name) || e.FullPath.Contains(g.RootFolder))
                        autoBackupGame = g;
                }

                if (autoBackupGame.RootFolder == null) {
                    var dir = new DirectoryInfo(autoBackupGame.Path);
                    autoBackupGame.RootFolder = dir.Name;
                }

                //Find the base directory of the file being changed and trim off
                //the unneeded pieces, e.g. "C:\Users\Rob\..."
                var indexOfGamePart = e.FullPath.IndexOf(autoBackupGame.RootFolder);
                var friendlyPath = e.FullPath.Substring(0, indexOfGamePart);
                var newPath = e.FullPath.Replace(friendlyPath, "\\");


                if (Directory.Exists(e.FullPath) && autoBackupGame != null) {
                    //True if directory, else it's a file.
                    //Do stuff for backing up a directory here.
                    //Currently nothing written here.
                }
                else { //If Directory.Exists is false, the path is a file.

                    try {
                        var copyDestinationFullPath = new FileInfo(_specifiedAutoBackupFolder + newPath);
                        if (!Directory.Exists(copyDestinationFullPath.DirectoryName))
                            Directory.CreateDirectory(copyDestinationFullPath.DirectoryName);
                        File.Copy(e.FullPath, copyDestinationFullPath.ToString(), true);
                    }
                    catch (FileNotFoundException ex) {
                        Logger.Log(ex); //My class, Logger, writes the exception text to a file.
                    }
                }
            }
            break;
        }
    }

person CubemonkeyNYC    schedule 12.02.2014    source источник
comment
Что именно говорит исключение?   -  person Lasse V. Karlsen    schedule 12.02.2014
comment
File.Copy не просто читает файл. Он его читает и записывает. Исключение, скорее всего, вызвано коротким интервалом. Возможно, вы пытаетесь скопировать временный файл, когда игра пытается удалить файл. Или вы можете попытаться скопировать файл игры, когда игра пытается перезаписать этот конкретный файл. Затем выдается ошибка, потому что игра и копия файла конфликтуют друг с другом.   -  person deathismyfriend    schedule 12.02.2014
comment
@Lasse, мне удалось использовать визуальную студию для отладки Terraria, и ошибка (перефразировано): Невозможно получить доступ к файлу, потому что он используется другой программой.   -  person CubemonkeyNYC    schedule 12.02.2014
comment
Что ж, если файл используется таким образом, что вы не можете его открыть, в чем вопрос?   -  person Lasse V. Karlsen    schedule 12.02.2014
comment
@deathismyfriend, поэтому File.Copy () читает источник и И записывает в него? Это неожиданно. Вы знаете, как избежать столкновения?   -  person CubemonkeyNYC    schedule 12.02.2014
comment
file.copy читает из одного файла и записывает в другой, но ему нужен доступ для чтения к исходному файлу. Если Terraria заблокировала этот файл, вы не сможете открыть его, пока он все еще используется Terraria.   -  person Lasse V. Karlsen    schedule 12.02.2014
comment
@ LasseV.Karlsen - Моя программа обращается к файлу сохранения, игра - нет. Итак, моя программа приводит к сбою процесса сохранения игры. Помимо использования текущей задержки, можете ли вы предложить способ, так сказать, уберечь мое приложение от игры?   -  person CubemonkeyNYC    schedule 12.02.2014
comment
В случае с этой конкретной игрой кажется, что она почему-то записывает и переписывает файлы сохранения. Вроде как дважды спасает. Итак, пока игра пишет / сохраняет второй раз, без таймеров моя программа OnChanged копирует файлы сохранения одновременно, поэтому они конфликтуют.   -  person CubemonkeyNYC    schedule 12.02.2014
comment
Я не совсем уверен, есть ли способ не знать, когда игра пытается сохранить в файл и ждать, пока сохранение не закончится. Тогда попробуйте скопировать. Изменить: попробуйте проверить это, похоже, хороший ответ на вопрос. stackoverflow.com/questions/1406808/   -  person deathismyfriend    schedule 12.02.2014
comment
@deathismyfriend: Это ему не поможет. Если он копирует, а затем приходит террария и пытается сохранить его, удалить, что угодно, у него будет точно такая же проблема. Это не проблема его программы, это проблема его программы, которая приводит к сбою Terraria.   -  person Colin DeClue    schedule 12.02.2014
comment
@ColinDeClue, это правда, я неправильно понял, когда он сказал, что его программа выдает ошибку. Я думал, что это его программа ошибалась.   -  person deathismyfriend    schedule 13.02.2014


Ответы (1)


В моем посте учтены комментарии о Terraria. Я не пробовал этого, но вот как я подошел бы к проблеме, если бы хотел получить наилучшие шансы скопировать файл, сохраняя при этом Terraria от сбоев.

Проблема

Я предполагаю, что Terraria дает сбой, потому что ему требуется доступ на запись к файлу пока вы пытаетесь его скопировать. Я также предполагаю, что File.Copy открывает файл без общего доступа, поэтому другой механизм открытия и копирование файла необходимо для решения проблемы.

Решение

Вам нужен какой-то способ открывать и читать из файла, оставляя Terraria право одновременно открывать и записывать в файл. Я не на 100% уверен, что этот подход сделает это, и это будет зависеть от того, как Terraria пытается открыть файл, но вы можете, по крайней мере, открыть файл, чтобы совместное использование по-прежнему было разрешено. Вместо File.Copy попробуйте:

using (FileStream inStream = new FileStream(filename, FileMode.Open, 
       FileAccess.Read, FileShare.ReadWrite) {
    // Specifying shared access as FileShare.ReadWrite is the key, as it lets 
    // Terraria open the file with write access.
    // Then add code here to copy the file out to your destination... 
    // this depends a bit on which version of .Net you are using, but 
    // inStream.CopyTo(outStream) is probably the easiest as long as you 
    // are using .Net 4+.
}

Вывод

Если ваше открытие не удалось, или ваша копия не работает (исключение), или ваш FileSystemWatcher сигнализирует во время копирования, это означает, что Terraria использует файл, и вам нужно будет повторить попытку позже. В любом случае, надеюсь, вы сможете держаться подальше от Террарии.

Мне очень интересно узнать, работает ли этот подход для вас. Прокомментируйте, если вы попробуете.

person Steven Hansen    schedule 12.02.2014
comment
Сработало именно так, как мне нужно. Похоже, что File.Copy () не имеет доступа к файлу, в отличие от вашего метода. В случае коллизии, что все еще маловероятно с моими таймерами задержки, приложение выдает перехватываемое исключение IOException вместо того, чтобы сбой игры. Копия все еще работает. Большое спасибо за идею. Это то, с чем я знал, что мне нужно работать (доступ / совместное использование), но у меня очень мало опыта. - person CubemonkeyNYC; 14.02.2014