Skip to main content

Command Palette

Search for a command to run...

System.IO - abstrakcja czy iluzja?

O ukrytej złożoności protokołów takich jak SMB, NFS czy FTP

Updated
4 min read
System.IO - abstrakcja czy iluzja?

Wprowadzenie

Pakiet System.IO dla .NET znacznie uproszcza zarządzanie plikami. Przykładowo, pozwala kopiować je jedną linijką kodu. Działa świetnie na lokalnym dysku. Niestety, gdy plik leży na zdalnym serwerze czy network share – proste API System.IO przestaje być tylko wygodną abstrakcją i zaczyna przypominać iluzję. Ukrywa bowiem rzeczywiste wyzwania integracji między systemami.

Złożoność (complexity) schowana w System.IO

System.IO to prawdziwy triumf abstrakcji. Jeden prosty interfejs do zarządzania plikami:

string content = File.ReadAllText("dokument.txt");
File.WriteAllText("kopia.txt", content);

Pod fasadą kryje się cała masa skomplikowanych operacji których nie chcemy obsługiwać tworząc nowe rozwiązania dla biznesu. Między innymi należą do nich:

  • automatyczne wykrywanie kodowania znaków,

  • buforowanie danych w pamięci,

  • zarządzanie uchwytami do plików (handles),

  • synchronizacja dostępu między procesami.

Wszystko działa świetnie w lokalnym środowisku Windows. Iluzja pojawia się, gdy używamy System.IO z zasobami przechowywanymi w sieci. Jest to nowa złożoność, której nie da się już zaprogramować na jeden sposób za prostą fasadą. Obsługa jej wymaga dodatkowej, decyzyjnej logiki od programisty.

Iluzja System.IO w sieci

Proste API System.IO sprawia wrażenie, że operacje na plikach są zawsze przewidywalne. Jednak w przypadku zasobów sieciowych to założenie z abstrakcji szybko zamienia się w iluzję.

Podstawowe operacje na pliku

Na lokalnym dysku zapis czy odczyt zwykle są szybkie i niezawodne:

// Zapis pliku – zwykle szybka operacja lokalnie
File.WriteAllText("plik.txt", content);

Na udziale sieciowym ta sama instrukcja może trwać sekundy lub nawet minuty. Pod spodem nie ma już zapisu na lokalny dysk tylko faktyczna transmisja danych przez protokół – SMB, NFS, FTP czy inny mechanizm udostępniania plików. To one decydują o tym, czy zapis będzie atomowy, czy zerwie się w połowie, jak obsłużone będą blokady i w jaki sposób raportowane są błędy. Fasada ukrywa całą tę złożoność, dając iluzję prostoty.

Zbyt długi czas operacji

W sieci, nawet prosta operacja może zająć więcej czasu. By zabezpieczyć system przed takimi przypadkami warto użyć metod asynchronicznych. W nich można przekazać CancellationToken i w ten sposób kontrolować timeout:

// Zapis pliku metodą asynchroniczną - obsługa części problemów przez przekazanie cancellationToken
cts.CancelAfter(3500);
WriteAllTextAsync("plik.text", content, cts.CancellationToken);

Użycie async nie rozwiązuje jednak problemu zanikającego połączenia czy potrzeby ponowienia operacji.

Ponowienie i zapis pośredni

Retry w przypadku niestabilnego połączenia to standard w komunikacji HTTP – w .NET zapewnia to np. biblioteka Polly. W przypadku integracji file transfer mechanizmy ponowienia pozostają jednak w gestii programisty.

Przykładowa implementacja prostego retry:

// Przykładowa implementacja ponawiania zapisu
int retries = 3;
for (int i = 0; i < retries; i++)
{
    try
    {
        File.WriteAllText(@"\\server\plik.txt", content);
        break;
    }
    catch (IOException) when (i < retries - 1)
    {
        Thread.Sleep(1000); // prosty backoff
    }
}

Drugim częstym wyzwaniem w protokołach SMB, NFS czy FTP jest brak jednoznacznej informacji o kompletności pliku. Jeśli drugi system reaguje na pojawienie się nowego zasobu na dysku sieciowym, często robi to jeszcze przed pełnym zakończeniem zapisu i zwolnieniem pliku. Prowadzi to do wyjątków przy próbie odczytu.

Typowym rozwiązaniem jest tworzenie pliku “.done” po zakończeniu operacji lub zapis do pliku tymczasowego “.tmp“, a dopiero na koniec zmiana nazwy na docelową:

// Zapis do pliku tymczasowego
File.WriteAllText("plik.txt.tmp", content);

// Dopiero po pełnym sukcesie – zmiana nazwy na docelową
File.Move("plik.txt.tmp", "plik.txt");

Opisane sytuacje pokazują, że przy pracy z plikami w sieci to programista musi świadomie przejąć kontrolę nad logiką i rozumieć złożoność ukrytą pod fasadą System.IO.

Podsumowanie

System.IO jest świetnym przykładem siły fasady, dopóki działamy w prostym, lokalnym środowisku. Wystarczy jednak przenieść pliki do sieci, by ta wygodna abstrakcja zaczęła przypominać iluzję. Problem nie leży tak naprawdę w samej bibliotece .NET, lecz w protokołach działających pod spodem – SMB, NFS czy FTP – które próbują symulować zachowanie lokalnego dysku, choć w rzeczywistości wykonują zawodny i kosztowny transfer sieciowy. To one decydują, czy zapis potrwa dwie sekundy czy dwie minuty, czy operacja zakończy się atomowo, czy w połowie, i jak zostaną obsłużone blokady.

W takich scenariuszach deweloper musi rozumieć ukrytą złożoność: świadomie nadpisać wybory podjęte pod fasadą albo opakować kod dodatkowymi mechanizmami (retry, zapis pośredni, kontrola timeoutów). Przykład System.IO pokazuje, że żadna abstrakcja nie potrafi całkowicie ukryć działania niższych warstw. Projektując własne interfejsy warto dążyć do maksymalnego uproszczenia, ale nie kosztem decyzji, które powinny pozostać w gestii programisty. Tam, gdzie próbujemy je ukryć, zamiast mocnej abstrakcji tworzymy jedynie iluzję.

More from this blog

B

Beniamin Lenarcik

13 posts