Zapis do jednego pliku przez wiele Tasków

Zapis do jednego pliku przez wiele Tasków
bakunet
  • Rejestracja:około 8 lat
  • Ostatnio:15 minut
  • Lokalizacja:Polska
  • Postów:1608
1

Posiadam odpalonych kilkanaście tasków, które nieustannie zapisują dane do kilku plików, po kilka razy na sekundę każdy. Próbuję znaleźć najlepsze rozwiązanie dla uniknięcia błędów związanych z dostępem do pliku.

Wstępnie sprawdzałem czy plik jest gotowy do zapisu:

Kopiuj
 private void SaveToFile(string line, string file)
        {
            if (File.Exists(file))
            {
                while (!IsFileReady(file))
                {
                    await Task.Delay(5);
                }
            }

            File.AppendAllText(file, line + Environment.NewLine);
        }
        private bool IsFileReady(string file)
        {
            try
            {
                using (FileStream inputStream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None))
                    return inputStream.Length > 0;
            }
            catch (Exception)
            {
                return false;
            }
        }

Jednak zdarzały się błędy, a mi osobiście trochę ciężko się to debuguje.

Następnie znalazłem poniższe rozwiązanie tutaj: https://stackoverflow.com/a/41262326/12603542

Kopiuj
private ReaderWriterLock _locker = new ReaderWriterLock();

private void SaveToFile(string line, string file)
        {
            try
            {
                _locker.AcquireWriterLock(10000); //You might wanna change timeout value 
                File.AppendAllText(file, line + Environment.NewLine);
            }
            finally
            {
                _locker.ReleaseWriterLock();
            }
        }

Jednak jeszcze dobrze go nie przetestowałem i ciekaw jestem czy macie jakieś inne sprawdzone podejścia?

edytowany 1x, ostatnio: bakunet
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
1

Pierwsze chyba nie powinno działać -- i jak piszesz -- nie działa.

Drugie jest oparte na blokowaniu (file locking) plików i rokuje nadzieje. :)

Takie rzeczy robi się właśnie blokowanie plików -- rodzaj semafora, który jest na danym pliku ustawiony (i jest to albo na poziomie systemu operacyjnego, albo biblioteki, albo w końcu można zrobić samemu, co zwykle źle działa :)).

Natomiast mam pytanie -- co zrobi Twoja funkcja SaveToFile z drugiego przykładu, gdy plik będzie zablokowany? Wydaje się, że po cichu nic nie zapisze i o tym się nawet nie dowiesz... :/

kzkzg
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 13 godzin
  • Postów:926
4

Może niech twoje taski wrzucają dane do jakiejś kolejki i niech jeden dedykowany Task ściaga z niej i zapisuję do pliku?


Keep calm and blame frontend.
Tell your cat I said pspsps.
bakunet
  • Rejestracja:około 8 lat
  • Ostatnio:15 minut
  • Lokalizacja:Polska
  • Postów:1608
0
koszalek-opalek napisał(a):

Natomiast mam pytanie -- co zrobi Twoja funkcja SaveToFile z drugiego przykładu, gdy plik będzie zablokowany? Wydaje się, że po cichu nic nie zapisze i o tym się nawet nie dowiesz... :/

Zaktualizowałem pierwszy przykład, gdzie kod się też znajduje w funkcji SaveToFile. I właściwie to masz rację, po 10s metoda przejdzie dalej. W pierwszym przykładzie po dłuższym czasie z jakiegoś powodu kod się zapętlał w while, nawet gdy plik był zwalniany przez inny Task, chciałem tego uniknąć w drugim przykładzie.

kzkzg napisał(a):

Może niech twoje taski wrzucają dane do jakiejś kolejki i niech jeden dedykowany Task ściaga z niej i zapisuję do pliku?

Myślałem o tym rozwiązaniu, ale odpada, ponieważ każdy Task pobiera też dane z tych plików, z ostatniej linii, więc muszą czekać na ich nadpisanie.

edytowany 1x, ostatnio: bakunet
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
0
bakunet napisał(a):
kzkzg napisał(a):

Może niech twoje taski wrzucają dane do jakiejś kolejki i niech jeden dedykowany Task ściaga z niej i zapisuję do pliku?

Myślałem o tym rozwiązaniu, ale odpada, ponieważ każdy Task pobiera też dane z tych plików, z ostatniej linii, więc muszą czekać na ich nadpisanie.

A to nie ma szans. :)

Jeśli dobrze rozumiem, Każdy Twój task musi czekać na to, co będzie w pliku, odczytać to i potem zapisać? W tej sytuacji, to tym bardziej jedyne rozwiązanie to zaproponowane przez @kzkzg -- wtedy ten task piszący będzie też mógł zwrócić innym taskom, co tam ostatnio się w pliku znalazło.

Ale wygląda na to, że nie potrzebujesz tak do końca czegoś takiego jak opisałeś, lecz jakiejś formy komunikacji między taskami... Napisz więc szerzej, bo mi to zaczyna wyglądać na problem XY: https://typeofweb.com/problem-xy-czyli-gdy-nie-wiemy-o-co-pytamy/

bakunet
  • Rejestracja:około 8 lat
  • Ostatnio:15 minut
  • Lokalizacja:Polska
  • Postów:1608
0

Więc dokładniej: Mam kilkanaście tasków, które wykonują zadania z daną z kolekcji. Każde zadanie ma inny czas wykonania, niektóre są wykonane szybciej, inne wolniej. Przed zaczęciem każdego zadania task zapisuje w pliku który indeks kolekcji danych wejściowych pobiera. Po wykonaniu zadania zapisuje wynik w pliku, tym samym co pozostałe taski też zapisują wyniki, i pobiera numer indeksu ostaniej wykonywanej iteracji przez dowolny z tasków, zaczyna wykonywać z kolejnym indeksem kolekcji itd. Tak więc każde kolejne wykonywane zadanie musi znać numer indeksu ostatniego zaczętego. Zapis do pliku jest potrzebny na wypadek wyłączenia aplikacji.

edytowany 2x, ostatnio: bakunet
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
1

@bakunet: To jeszcze pytanie pomocnicze -- jakie wyłączenie aplikacji masz na myśli? Normalne (wtedy można taki zapis oprogramować na jakimś onexit czy tym podobnym zdarzeniu/wyjątku) czy brak zasilania (wtedy i tak Ci tracisz ostatnie zmiany w pliku, bo były jeszcze w buforze systemowym)?

W obu przypadkach potrzebny Ci jakiś task master, który z jednej strony nadzoruje, co mają robić pozostałe wątki, a z drugiej zapisuje do pliku co jest ukończone (zamykając plik od razu, żeby nie tracić za dużo danych w razie awarii).

bakunet
  • Rejestracja:około 8 lat
  • Ostatnio:15 minut
  • Lokalizacja:Polska
  • Postów:1608
0

@koszalek-opalek: Brzmi dobrze, choć nie mam jeszcze pojęcia jak stan miałby być zapisany w razie utraty zasilania / systemu. Czy przez zamykając plik od razu masz na myśli dopisywanie / nadpisywanie na bieżąco? A na myśli miałem przewidziane i nieprzewidziane wyłączenie aplikacji, co by nie wykonywać ponownie całej pracy w wypadku gdy Windows zdecyduje że jest teraz najlepsza pora na aktualizację i restart systemu :)

edytowany 1x, ostatnio: bakunet
Miang
  • Rejestracja:około 7 lat
  • Ostatnio:minuta
  • Postów:1676
0

w programie pod linuxem w podobnym celu jest użyty syslog ale nie wiem czy jest jakiś odpowiednik dla windows


dzisiaj programiści uwielbiają przepisywać kod z jednego języka do drugiego, tylko po to by z projektem nadal stać w miejscu ale na nowej technologii
VA
  • Rejestracja:ponad 7 lat
  • Ostatnio:16 dni
2

Jeśli zdecydowałeś się na użycie ReaderWriteLocka to radziłbym użyć ReaderWriterLockSlim. Ale w tym przypadku użycia nie ma to wielkiego sensu, bo nie używasz go w kontekście read/write to którego był tworzony a tylko blokujesz zasób do zapisu. Szkoda więc tracić czas na overhead jaki narzuca użycie tej klasy, lepiej (szybciej) będzie to działać przy użyciu Monitora (który też można timeoutować w razie potrzeby, tak jak jest w twoim kodzie).

Ale może lepiej byłoby podejsć do tego w inny sposób i skorzystać z jakiegoś bufora do którego wszystkie zainteresowane wątki mogłyby ładować dane a ten co jakiś czas byłby zrzucany do pliku. Tak chyba działają bilblioteki logujące (nie mam pewności bo nie analizowałem ich źródeł, może mają jeszcze lepsze podejście).

Btw, z użyciem mechanizmu jaki zaprezentowałeś to musisz uważać. Jeśli chcesz blokować coś w aplikacji wielowątkowej to musisz pamiętać o tym, że żeby ReaderWriterLock coś rzeczywiście blokował to musi być współdzielony przez wszystkie wątki, w podanym przykładzie wygląda to tak że niestety nie jest (chyba że cała klasa obsługująca ten zapis jest odpowiednio użyta albo zarejestrowana w DI) i raczej nie wygląda na to że zadziała w sposób jakiego oczekujesz.

bakunet
  • Rejestracja:około 8 lat
  • Ostatnio:15 minut
  • Lokalizacja:Polska
  • Postów:1608
0

@var: Pomysł z buforem ciekawy, co jakiś czas można by wykonywać zrzut do plików danych + indeksu. A wątki pobierały by wartość indeksu z pamięci. To by mnie zadowoliło w 100%. A co do blokowania zapisu pliku, to bez obawy, wszystkie wątki widzą tego lockera.

JP
  • Rejestracja:ponad 7 lat
  • Ostatnio:5 miesięcy
  • Postów:1065
2

A po Co ten plik? Bo jeśli tylko do komunikacji między taskami to można to lepiej zrobić.

G1
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 9 godzin
  • Postów:506
1

@jacek.placek: ten plik jest po to, żeby w razie niespodziewanego shutdownu nie utracić postępu

JP
  • Rejestracja:ponad 7 lat
  • Ostatnio:5 miesięcy
  • Postów:1065
0

Przetestuj jakąś thread safe kolekcję do przechowywania danych w trakcie pracy programu. https://docs.microsoft.com/pl-pl/dotnet/standard/collections/thread-safe/
Do pliku (lokalnej bazy?) zapisuj dane już przetworzone przez te wątki. Może jakiś osobny wątek do zapisywanie tej kolekcji thread safe do pliku albo jakiś timer zapisujący kolekcję.

xxx_xx_x
  • Rejestracja:prawie 13 lat
  • Ostatnio:3 dni
  • Postów:365
1

@gswidwa1: takie coś najlepiej robić przez bazę danych.

  1. Jeden task produkuje kolejkę
  2. Drugi task odczytuje z kolejki ale nie usuwa (!)
  3. Przetwarza task
  4. Robi w transakcji zapis wyniku i usunięcie zadania
    Jeżeli coś pójdzie nie tak zadanie zostanie w kolejce. Jeżeli zadania nie ma to na pewno jest wynik bo to gwarantuje transakcja bazodanowa
JP
@bagnet pisze o kilkunastu wątkach liczących. Kilkanaście wątków ma sprawdzać w db czy jest nowe zadanie? To nie wyglada jak najlepsze rozwiązanie Db jako log to tak ale po obliczeniach.
xxx_xx_x
Tak się to często robi, alternatywa jest użycie czegoś bardziej zaawansowanego jak np kafka
xxx_xx_x
Jak chcesz mieć pewność że wszystkie zadania zostaly przetworzone i nie uszkodzone to jak najbardziej jest ok. Pliki ci tego nie zagwarantują, tzn napisanie tego nie jest takie proste, baza danych daje ci to od ręki. Poza tym dystrybucja tych zadań może zająć się jeden wątek
JP
Sorry. Usunąłem swój komentarz bo nie widziałem Twojej odpowiedzi. IMO @var załatwił temat, pomijając użycie plików, których bym w tu nie użył. Mi nie chodzi o nieużywane db tylko o Twój pomysł, nasluchiwaniem przez wątek czy jest zadanie do liczenia, jeśli to miałeś na myśli w pkt2, bo albo nie będzie miał co robić albo może się nie wyrabiać. Zależy od specyfiki problemu
xxx_xx_x
Nie musisz nasłuchiwać, na starcie lecisz i odczytywać kolejce zadania z bazy. Jak się skończą to możesz czekać na standardowa komunikację od procesu. Baza tylko dba o to by wszystko było prawidłowo obsługuje i odporne na crashe. Dlatego sam b odczyt zadania i zapis wyniku leci przez bazę
kzkzg
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 13 godzin
  • Postów:926
0

Obczaj czy Hangfire nie jest tym czego potrzebujesz.


Keep calm and blame frontend.
Tell your cat I said pspsps.
WE
raczej overkill
GZ
  • Rejestracja:około 4 lata
  • Ostatnio:około rok
  • Postów:35
0

Do takiego zadania użyłbym loggera, np. NLog.
Sprawdzone, wydajne, przetestowane.
Dodatkowo nie tylko wiele tasków może zapisywać do tego jednego pliku, ale również wiele procesów.

Oczywiście można zrobić usługę implementującą jakiś interfejs aby ukryć tą implementację.

G1
  • Rejestracja:ponad 4 lata
  • Ostatnio:około 9 godzin
  • Postów:506
0

Jeżeli wątków jest tylko kilkanaście to może niech każdy wątek zapisuje po prostu do oddzielnego pliku w wydzielonym folderze i na koniec się je połączy wszystkie 😂

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.