Jak testować czy 2 procesy nie tłuką się przy dostępie do plików?

0

Wymóg: Nie ma gwarancji, że kilka instancji tego samego programu nie będzie chciało czytać/pisać do tego samego pliku na dysku niemal w tym samym czasie. Napisać program tak, by nie stanowiło to problemu.

Zastanawiam się, jak napisać tu test?

Mamy na przykład takie miejsce w kodzie:

public class MojaKlasa
{
    public void MojaMetoda()
    {
        using (var str = File.Open(plik, FileMode.CreateNew, FileAccess.Write, FileShare.None)
        using (var writer = new StreamWriter(str))
        {
            writer.Write(cośtam);
        }
    }
}

Widać, że jest tu gorący punkt.

Pomysł na testowanie mam taki, ale wydaje mi się on być brzydki:

public class MojaKlasa
{
    private bool blokujPlikDługo;

    public MojaKlasa(bool uwagaTenParametrSłużyTylkoDoTestowaniaNieUżywaćRozwalaKlasęISprawiaŻeDziałaWściekleDługo=false) =>
        blokujPlikDługo = uwagaTenParametrSłużyTylkoDoTestowaniaNieUżywaćRozwalaKlasęISprawaŻeDziałaWściekleDługo;

    public void MojaMetoda()
    {
        using (var str = File.Open(plik, FileMode.CreateNew, FileAccess.Write, FileShare.None)
        using (var writer = new StreamWriter(str))
        {
            if(blokujPlikDługo)
                Thread.Sleep(500);
            writer.Write(cośtam);
        }
    }
}

I teraz w teście uruchamiamy 2 wątki: jeden wątek tworzy instancję MojaKlasa z tym parametrem ustawionym na true i odpala MojaMetoda, drugi wątek wpierw czeka 250ms a następnie tworzy instancję MojejKlasy z parametremm ustawionym na false i odpala MojąMetodę. Sprawdzamy, czy drugi wątek walnie wyjątkiem jak należy i czy plik jest zapisany poprawnie (tylko przez jeden wątek), bez krzaczków.

Jaka jest alternatywa?

1

Z wyjątkiem jest ten problem że nie wiesz czy dostałeś wyjątek bo nie masz uprawnień do pliku czy inny proces po nim czyta, można sprawdzać jaki to jest wyjątek - ale nie tędy droga.

Rozwiązaniem będzie synchronizowanie dostępu do pliku.

MSDN lock

Coś takiego:

    public class MojaKlasa
    {
        public void MojaMetoda()
        {
            lock (plik)
            {
                using (var str = File.Open(plik, FileMode.CreateNew, FileAccess.Write, FileShare.None)
                    using (var writer = new StreamWriter(str))
                    {
                        writer.Write(cośtam);
                    }
            }
        }
    }

Przy czym to też nie jest do końca dobrze, bo synchronizacja na stringach to kiepski pomysł - ale będzie działać.
Tutaj masz o synchronizacji i stringach Using string as a lock to do thread synchronization, to powinno Cię nakierować jak to napisać.

1

Czy to jest pełna treść zadania ? Bez szerszego kontekstu to zadanie jest bez sensu bo zawsze może dojść do sytuacji kiedy coś się wykrzaczy w szczególności jeśli mówimy o programie, który czyta i zapisuje z tego samego jednego pliku.
Jeśli to pytanie ogólne to najlepiej aby różne instancje nie pisały do tych samych plików ( z odczytem nie widzę problemu ) i niech tworzą pliki z unikalnymi "timestamp", które generowane są przy uruchomieniu programu.

0

@Wilktar: O ile rozumiem to NIe muszą być wątki tego samego procesu. W konsekwencji lock nie pomoże.

@katakrowa: Nie ja odpowiadam za wymagania. Na moje rozumienie ktoś chce traktować system plików jako bazę danych. Niemniej wbrew pozorom zadanie wydaje się być możliwe do zrealizowania, gdyż - jak się właśnie nauczyłem - na poziomie OS jest mechanizm blokowania blików na zasadzie czytelników i pisarzy, takie rzeczy jak FileShare.None udostępniają ten mechanizm na poziomie C#

@somekind: Nie, przyznaję, że nie wiedziałem o tym, dzięki za info. NIemniej, w czym to jest lepsze niż FileShare.None, które mam obecnie? W jednym i drugim wypadku otrzymujemy IOException, jeśli próbujemy pisać/czytać do/z pliku, który jest zablokowany przez inny proces - czyli jakby sytuacja bez zmian. Tzn FileStream.Lock pozwala na blokowanie tylko fragmentów pliku a nie całego pliku (jak FileShare.None), ale to akurat nie jest mi potrzebne.

0
kmph napisał(a):

jak się właśnie nauczyłem - na poziomie OS jest mechanizm blokowania blików na zasadzie czytelników i pisarzy, takie rzeczy jak FileShare.None

Owszem rozwiązuje to problem zapisu przez wiele instancji ale to nie jest koniec problemów a w zadaniu jest wyraźnie zaznaczone, że:

że kilka instancji tego samego programu nie będzie chciało czytać/pisać(...) Napisać program tak, by nie stanowiło to problemu.

Rozwiązanie wielodostępu do plików to nie tyko wyeliminowanie jednoczesnych zapisów / odczytów pliku. Temat jest bardziej rozległy.
Zastosowanie powyższych "bloakad" w żaden sposób nie rozwiązuje problemów z odczytywaniem pliku, do którego w danym czasie pisze inna aplikacja. Czytelnik może czytać a w tym czasie inny proces może zmienić coś "na początku" pliku ( przecież w zadaniu nie ma mowy o tym, że do plików informacja może być tylko dopisywana ). Jeśli plik, na którym pracujemy ma np 4GB to jego każdorazowe wczytywanie i analizowanie od początku nie ma sensu.
Dlatego uważam, że podane założenia bez ich doprecyzowania są problemem typowo akademickim.

0
  1. Nie, to akurat nie jest problem akademicki.
  2. Pliki są małe, zapomniałem tego napisać a to rzeczywiście istotnie. Stąd apka operuje w ten sposób, że czyta je na raz w całości i też w całości do nich pisze.
  3. Sprawdziłem - czytanie z pliku też rzuca wyjątkiem, jeśli pisarz założył FileShare.None
1

Sorry zasugerowałem się:

I teraz w teście uruchamiamy 2 wątki:

Jeżeli to jest kilka instancji tego samego programu to może użyć: named mutex

Coś takiego?

       static void WriteToFile(string file)
        {
            using (Mutex mutex = new Mutex(false, file))
            {
                try
                {
                    mutex.WaitOne();
                    using (var str = File.Open(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
                    {
                        using (var writer = new StreamWriter(str))
                        {
                            long endPoint = str.Length;
                            str.Seek(endPoint, SeekOrigin.Begin);
                            //Testowo
                            Thread.Sleep(TimeSpan.FromSeconds(5));
                            writer.Write($"TEST {DateTime.Now}" + Environment.NewLine);
                        }
                    }
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
        }

1 użytkowników online, w tym zalogowanych: 0, gości: 1