czwartek, 31 maja 2012

Jak uniknąć III wojny światowej... czyli operacje na plikach.

Zdarza się podczas zapisu/odczytu danych z pliku zamiast pożądanego efektu otrzymujemy wyjątek IOException. Spowodowane jest to próbą równoległego wykonania operacji na tym samym pliku przez różne instancję programu.



Wyobraźmy sobie hipotetyczną sytuację:
  • Program A zapisuje dane do pliku blokując go dla innych programów
  • Program B próbuje odczytać dane z zablokowanego pliku i ... BUUUUM!!! 
  • Program B transferujący dane do lotniskowca przerywa działanie. 
  • Kapitan podejrzewając wrogi atak na bazę odpala rakiety ziemia-powietrze w kierunkach strategicznych.
  • Zaczyna się III wojna światowa......
A tak łatwo można tego uniknąć!

Rozwiązaniem umożliwiającym uratowanie świata przed zagładą jest metoda wyczekującą na odblokowanie pliku.

Inicjujemy zmienną typu AutoResetEvent, która zajmuje się zatrzymywaniem wątku.
var autoResetEvent = new AutoResetEvent(false);
Próbujemy otworzyć plik poleceniem File.Open, jeśli nam się to uda to robimy z plikiem to co chcemy i hasta la vista, baby.
                    using (FileStream file = File.Open(path, fm, fa, fs))
                    {
                        action(file);
                        break;
                    }
Całe rozwiązanie znajdujemy w sytuacji kiedy plik jest zablokowany i dostajemy wyjątek IOException.
Inicjujemy obiekt typu FileSystemWatcher, nasłuchujący zmian katalogu zawierającym nasz plik.
                    var fileSystemWatcher =
                        new FileSystemWatcher(Path.GetDirectoryName(path))
                        {
                            EnableRaisingEvents = true
                        };
Zatrzymujemy wątek w oczekiwaniu na odblokowanie pliku:
autoResetEvent.WaitOne();
Gdy plik zostanie już odblokowany autoResetEvent ponownie uruchomi działanie wątku:
autoResetEvent.Set();
i cała operacja zacznie się od początku.
Poniżej cały, kod funkcji dla tych, którzy już od zaraz chcą ratować świat :)
        public static void OpenIfNotLocked(string path, Action<FileStream> action,
            FileMode fm = FileMode.OpenOrCreate,
            FileAccess fa = FileAccess.ReadWrite,
            FileShare fs = FileShare.None)
        {
            var autoResetEvent = new AutoResetEvent(false);

            while (true)
            {
                try
                {
                    using (FileStream file = File.Open(path, fm, fa, fs))
                    {
                        action(file);
                        break;
                    }
                }
                catch (IOException)
                {
                    var fileSystemWatcher =
                        new FileSystemWatcher(Path.GetDirectoryName(path))
                        {
                            EnableRaisingEvents = true
                        };

                    fileSystemWatcher.Changed +=
                        (o, e) =>
                        {
                            if (Path.GetFullPath(e.FullPath) == Path.GetFullPath(path))
                            {
                                autoResetEvent.Set();
                            }
                        };

                    autoResetEvent.WaitOne();
                }
            }
        }

Brak komentarzy:

Prześlij komentarz