Zapis do jednego pliku przez wiele Tasków

Zapis do jednego pliku przez wiele Tasków
bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:Polska
  • Postów:1596
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:około 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 4 godziny
  • Postów:924
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:prawie 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:Polska
  • Postów:1596
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:około 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:prawie 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:Polska
  • Postów:1596
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:około 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:prawie 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:Polska
  • Postów:1596
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:prawie 7 lat
  • Ostatnio:2 minuty
  • Postów:1659
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:około 5 godzin
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:prawie 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:Polska
  • Postów:1596
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:4 miesiące
  • 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:około 4 lata
  • Ostatnio:23 dni
  • Postów:504
1

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

JP
  • Rejestracja:ponad 7 lat
  • Ostatnio:4 miesiące
  • 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:9 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 4 godziny
  • Postów:924
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:około 4 lata
  • Ostatnio:23 dni
  • Postów:504
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 😂

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)