2012-01-22 6 views
24

Pracuję nad oprogramowaniem serwera, które okresowo potrzebuje zapisać dane na dysku. Muszę się upewnić, że stary plik zostanie nadpisany i że plik nie może zostać uszkodzony (na przykład tylko częściowo nadpisany) w razie nieoczekiwanych okoliczności.Niezawodne zapisywanie plików (File.Replace) w pracowitym środowisku

Mam przyjął następującą wzoru:

string tempFileName = Path.GetTempFileName(); 
// ...write out the data to temporary file... 
MoveOrReplaceFile(tempFileName, fileName); 

... gdzie MoveOrReplaceFile jest:

public static void MoveOrReplaceFile(string source, string destination) { 
    if (source == null) throw new ArgumentNullException("source"); 
    if (destination == null) throw new ArgumentNullException("destination"); 
    if (File.Exists(destination)) { 
     // File.Replace does not work across volumes 
     if (Path.GetPathRoot(Path.GetFullPath(source)) == Path.GetPathRoot(Path.GetFullPath(destination))) { 
      File.Replace(source, destination, null, true); 
     } else { 
      File.Copy(source, destination, true); 
     } 
    } else { 
     File.Move(source, destination); 
    } 
} 

Działa to dobrze, o ile serwer ma wyłączny dostęp do plików. Jednak File.Replace wydaje się być bardzo wrażliwy na zewnętrzny dostęp do plików. Za każdym razem mój program działa na systemie z programu antywirusowego lub systemu tworzenia kopii zapasowych w czasie rzeczywistym, losowe błędy File.Replace zaczynają pojawiać się:

System.IO.IOException: Nie można usunąć plik należy wymienić.

Oto kilka możliwych przyczyn, które mam wyeliminowane: uchwyty

  • Niepublikowany plik: using() zapewnia, że ​​wszystkie uchwyty plików są uwalniane jak najszybciej.
  • Problemy z wątkami: lock() chroni dostęp do każdego pliku.
  • Różne woluminy dyskowe: Funkcja File.Replace() kończy się niepowodzeniem, gdy używana jest na woluminach dyskowych. Moja metoda sprawdza to już i wraca do File.Copy().

A oto kilka wskazówek, które natknąłem, i dlatego raczej nie ich użyć:

  • Volume Shadow Copy Service: To działa tylko tak długo, jak problematyczne trzeciej oprogramowanie stronnicze (monitory kopii zapasowych i antywirusowych itp.) również używają VSS. Korzystanie z VSS wymaga tonów P/Invoke i ma problemy związane z platformą.
  • Blokowanie plików: W C# blokowanie pliku wymaga zachowania otwartego strumienia FileStream. Zachowałoby to oprogramowanie innych firm, ale 1) nadal nie będę w stanie zastąpić pliku za pomocą File.Replace, i 2) Jak wspomniałem powyżej, wolałbym najpierw napisać do pliku tymczasowego, aby uniknąć przypadkowego korupcja.

Byłbym wdzięczny za wszelkie dane wejściowe dotyczące pobierania pliku.Replace do pracy za każdym razem lub, bardziej ogólnie, niezawodnie zapisywania/zastępowania plików na dysku.

+1

Czy można oczekiwać 'MoveOrReplaceFile' być uruchamiane jednocześnie (co oznacza dostęp przez wielu wątków aplikacji * * lub nawet z wieloma instancjami aplikacji)? –

+1

Mój własny kod nigdy nie wywołuje operacji MoveOrReplaceFile jednocześnie, ale są inne procesy poza moją kontrolą, które mogą próbować odczytać plik. Te przypadkowe dostępu do odczytu powodują niepowodzenie File.Replace. – matvei

Odpowiedz

26

You naprawdę chcesz używać 3rd parametr nazwę pliku kopii zapasowej. Dzięki temu system Windows może po prostu zmienić nazwę oryginalnego pliku bez konieczności jego usuwania. Usunięcie zakończy się niepowodzeniem, jeśli jakikolwiek inny proces spowoduje otwarcie pliku bez udostępniania skasowania, zmiana nazwy nigdy nie stanowi problemu. Następnie można usunąć go samodzielnie po wywołaniu Replace() i zignorować błąd. Usuń go również przed wywołaniem Replace(), aby zmiana nazwy nie zakończyła się niepowodzeniem i oczyszczenie nie powiodło się wcześniejszymi próbami. Tak z grubsza:

string backup = destination + ".bak"; 
File.Delete(backup); 
File.Replace(source, destination, backup, true); 
try { 
    File.Delete(backup); 
} 
catch { 
    // optional: 
    filesToDeleteLater.Add(backup); 
} 
+0

Hans, czy mógłbyś pokazać kod dla tego wzorca? To może stać się kanoniczną odpowiedzią na to pytanie w przyszłości. –

+0

Tego właśnie szukałem! Dziękuję bardzo. – matvei

+2

Czy to nie przesuwa problemu o jeden poziom? Co się stanie, jeśli nie można usunąć pliku kopii zapasowej? – grantnz

1

Jeśli oprogramowanie pisze na partycji NTFS, spróbuj użyć Transactional NTFS. Możesz użyć AlphFS dla wrapper .NET do API. Jest to prawdopodobnie najbardziej niezawodny sposób zapisywania plików i zapobiegania korupcji.

+0

Działa jednak tylko w systemie Vista i nowszych wersjach. – CodesInChaos

+1

'File.Replace()' pod mono magicznie robi rzeczy takie jak używanie POSIX 'rename()' na innych systemach. Czy odejście od klas wbudowanych nie komplikuje przenośności? Ponadto, jeśli 'File.Replace()' faktycznie działało jako udokumentowane i niezawodnie, to już zapewnia niezbędną funkcjonalność ... – binki

2

Istnieje kilka możliwych podejść, oto niektóre z nich:

  1. Użyj „lock” plik - plik tymczasowy, który jest tworzony przed operacją i wskazuje innych autorów (lub czytelników), że plik jest zmodyfikowane, a zatem wyłącznie zamknięte.Po zakończeniu operacji - usuń plik blokady. Ta metoda zakłada, że ​​polecenie tworzenia pliku ma charakter atomowy.
  2. Użyj interfejsu transakcyjnego NTFS (jeśli jest to konieczne).
  3. Utwórz łącze do pliku, napisz zmieniony plik pod losową nazwą (na przykład Guid.NewGuid()) - a następnie zmień ponownie link do nowego pliku. Wszyscy czytelnicy będą mieli dostęp do pliku za pośrednictwem linku (którego nazwa jest znana).

Oczywiście wszystkie 3 metody mają swoje wady i zalety

Powiązane problemy