fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
4 minut

Problem z FileSystemWatcher.Created


09.05.2008

Klasa System.IO.FileSystemWatcher jest momentami wprost niezastąpiona. Nie będę opisywał tutaj jej cech, ale zajmę się jednym problemem. Zdarzenie Created daje nam znać o tym, że nowy plik pojawił się w obserwowanym katalogu. Co się jednak może stać, gdy beztrosko zaczniemy się owym plikiem zajmować? Prawdopodobne jest, że otrzymamy wyjątek. Powód takiego zachowania jest taki, że zdarzenie Created informauje nas o momencie UTWORZENIA pliku, a nie jego GOTOWOŚCI DO OBRÓBKI. W przypadku większych plików od momentu utworzenia go w katalogu do zakończenia procesu kopiowania jego zawartości może się przesypać sporo piachu w klepsydrze Piaskowego Dziadygi. Dlatego też stworzyłem klasę dziedziczącą z Watchera, która udostępnia zdarzenie AfterCreated – odpalane w momencie zakończenia tworzenia nowego pliku.
Niestety programiści tworzący FileSystemWatcher nie do końca dostosowali się do praktyk związanych z tworzeniem zdarzeń i metody OnCreated, OnRenamed itd nie są oznaczone jako wirtualne. Dlatego też podpinam się do zdarzenia Created. Po jego wystąpieniu w nowym wątku próbuję otworzyć docelowy plik – jeśli się udaje to odpalam swoje zdarzenie AfterCreated. Jeżeli nie – przechwytuję wyjątek, czekam jakiś czas i próbuję znowu. I tak w koło Macieju. Pod uwagę wzięty został równiez scenariusz usunięcia pliku w okresie pomiędzy końcem kopiowania a sprawdzeniem dostępności. W tym przypadku łapię wyjątek FileNotFoundException i kończę wątek. W celu uniknięcia problemów z wątkami i aktualizacją UI wykorzystany został mechanizm oferowany przez klasy AsyncOperation/AsyncOperationManager.
ENJOY:


 1:   public class FileSystemWatcherEx : FileSystemWatcher
2: {
3: public event EventHandler<FileSystemEventArgs> AfterCreated;
4: protected virtual void OnAfterCreated(FileSystemEventArgs e)
5: {
6: if (AfterCreated != null)
7: AfterCreated(this, e);
8: }
9:
10: private const int DEFAULT_CHECK_AVAILABILITY_INTERVAL = 500;
11: private int _checkAvailabilityInteval = DEFAULT_CHECK_AVAILABILITY_INTERVAL;
12: [DefaultValue(DEFAULT_CHECK_AVAILABILITY_INTERVAL)]
13: [Category(“Behavior”)]
14: [Description(“Determines how ofter the file is checked for availability.”)]
15: public int CheckAvailabilityInteval
16: {
17: get { return _checkAvailabilityInteval; }
18: set { _checkAvailabilityInteval = value; }
19: }
20:
21: public FileSystemWatcherEx()
22: {
23: base.Created += (sender, e) =>
24: {
25: AsyncOperation operation = AsyncOperationManager.CreateOperation(null);
26:
27: // start a new thread to watch the file
28: ThreadPool.QueueUserWorkItem(delegate
29: {
30: bool canOpen = false;
31: // loop while the file cannot be opened -> is still being used by another process
32: while (canOpen == false)
33: {
34: try
35: {
36: // try to open the file for reading – and close it immidiately if succeeded
37: File.OpenRead(e.FullPath).Close();
38: canOpen = true;
39: }
40: // can occur when a file is removed directly after processing
41: catch (FileNotFoundException)
42: {
43: break;
44: }
45: // occurs when trying to open a directory rather than a file
46: catch (UnauthorizedAccessException)
47: {
48: break;
49: }
50: catch (IOException)
51: {
52: // wait and try again
53: Thread.Sleep(CheckAvailabilityInteval);
54: }
55: }
56: if (canOpen)
57: {
58: operation.Post(delegate
59: {
60: OnAfterCreated(e);
61: }, null);
62: }
63: });
64: };
65: }
66: }

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
apl
apl

Zdarzenie [b]Create[/b] jest wywoływane również wówczas, gdy utworzony został nowy folder – uważaj na to. Ja osobiście próbowałbym rozwiązać problem inaczej, korzystając z pewnego charakterystycznego zachowania [b]FileSystemWatcher[/b] – po zdarzeniu [b]Created[/b] wywoływane jest [i]przynajmniej raz[/i] zdarzenie [b]Changed[/b]. Niestety nie da się przewidzieć, ile takich zdarzeń wystąpi podczas tworzenia pliku, można natomiast w metodzie obsługi zdarzenia [b]Created[/b] podpiąć się pod zdarzenie [b]Changed[/b] i w nim sprawdzać, czy plik da się otworzyć, najlepiej żądając prawa dostępu do pliku na wyłączność (tj. wywołując [b]File.Open([i]ścieżka[/i], FileMode.Open, FileAccess.Read, FileShare.None)[/b]). Gdy żądanie zostanie spełnione, można odpiąć handler i wywołać zdarzenie informujące o dostępności pliku.

Tom
Tom

:) No i nie przeczytam drugiego akapitu :)

Procent

@Apl: faktycznie, umknął mi fakt że dla Directory zostanie odpalone to samo. Co do rozwiązania z Changed – wyglądałoby mniej szpanersko niż te wszystkie AsyncOperation, ThreadPool, anonimowych metod i wyrażeń lambda;).

@Tom: co z drugim akapitem? znika czasami? ;)

Procent

@Apl
Po chwili namysłu: do rozwiązania z Created trzeba by było dodać listę aktualnie obserwowanych plików tak, aby zdarzenie AfterCreated nie zostało wywołane dla już istniejącego pliku zmodyfikowanego podczas tworzenia nowego pliku. Coś w stylu:

// watcher_Created:
_currentlyObservedFiles.Add(e.Name);
// watcher_Changed:
if (_currentlyObservedFiles.Contains(e.Name))
{
_currentlyObservedFiles.Remove(e.Name);
OnAfterCreated(e);
}

A co do katalogu zamiast pliku… Do tej chwili byłem przekonany, że zostanie to wychwycone w FileNotFoundException. Okazuje się jednak, że jest wówczas wyrzucany UnauthorizedAccessException. Potem odpowiednio zaktualizuję posta.

Tom
Tom

No już doczytałem, przerwa spowodowana "Piaskowym Dziadygą"

apl
apl

[i](…) do rozwiązania z Created trzeba by było dodać listę aktualnie obserwowanych plików tak, aby zdarzenie AfterCreated nie zostało wywołane dla już istniejącego pliku zmodyfikowanego podczas tworzenia nowego pliku[/i] Niekoniecznie, wystarczy stworzyć prostą klasę: private class FileChangedWatcher { public string FullPath { get; private set; } public FileSystemWatcherEx FileSystemWatcher { get; private set; } public FileChangedWatcher(FileSystemWatcherEx watcher, string path) { FullPath = path; FileSystemWatcher = watcher; } public void Attach() { FileSystemWatcher.Changed += HandleFileChanged; } public void Unattach() { FileSystemWatcher.Changed -= HandleFileChanged; } private void NotifyFileReady() { FileSystemWatcher.NotifyFileReady(FullPath); } private void HandleFileChanged(object sender, FileSystemEventArgs e) { if (e.FullPath == FullPath)… Read more »

Procent

Gdyby nie brak FullTrust na serwerze to dodanie przycisku BuildAll do bloga nie byłoby wielkim problemem:).

A propozycja dodatkowej klasy – ciekawa, OK i w ogóle, ale moim zdaniem w tym konkretnym przypadku strzelasz z armaty do wróbli.

apl
apl

Kwestia podejścia, osobiście nie porównałbym tego rozwiązania ani do armaty, ani nawet do karabinu wielkokalibrowego, z którego w finale najnowszej części "Rambo" tytułowy bohater rozwala tabuny birmańskiej piechoty. Obydwa rozwiązania są tego samego kalibru, wszystko zależy od tego, co chcemy osiągnąć. Jeśli chcemy zastosować strategię [i]push[/i], wówczas polegamy na zdarzeniach zgłaszanych przez [b]FileSystemWatcher[/b]. Jeśli chcemy zastosować strategię [i]pull[/i], wówczas stosujemy polling w nowym wątku. Pytanie, którą ze strategii uznajemy w tym przypadku za lepszą? Jak już zdążyłeś zauważyć, skłaniam się ku rozwiązaniu w modelu push. Główną jego zaletą jest to, że o dostępności pliku zostaniemy poinformowani możliwie najwcześniej, jednocześnie implementacja… Read more »

mick
mick

nie znam c# a potrzebowałbym przetłumaczyć taką klasę na c++ niestety nie rozumiem konstrukcji
base.Created += (sender, e) =>
czy może ktoś napisać o tym kilka zdań / wskazać miejsce gdzie znajdę o tym info??

procent

@mick:
Kluczowe pojęcia: delegaty (delegates) i zdarzenia (events), tutaj jest art w kontekście C++: http://www.functionx.com/vcnet/topics/delevents.htm . Z kolei (sender, e) => to wyrażenie lambda, nie wiem czy jest w c++ odpowiednik takiej konstrukcji… ale możesz zamiast tego napisać zwykłą metodę która przyjmuje argumenty object sender i EventArgs e.

Moja książka

Facebook

Zobacz również