Mechanizm synchronizacji obiektu obserwowalnego

Wyobraźmy sobie, że musimy zsynchronizować dostęp do odczytu / zapisu do zasobów udostępnionych. Wiele wątków uzyska dostęp do tego zasobu zarówno w trybie odczytu, jak i zapisu (większość czasu na czytanie, czasem na pisanie). Załóżmy również, że każdy zapis zawsze wywoła operację odczytu (obiekt jest obserwowalny).

W tym przykładzie wyobrażam sobie taką klasę (wybacz składnię i styl, tylko dla celów ilustracyjnych):

class Container {
    public ObservableCollection<Operand> Operands;
    public ObservableCollection<Result> Results;
}

Kusi mnie, żeby użyćReadWriterLockSlim w tym celu wrzuciłbym to do wiadomościContainer poziom (wyobraź sobie, że obiekt nie jest taki prosty, a jedna operacja odczytu / zapisu może obejmować wiele obiektów):

public ReadWriterLockSlim Lock;

WdrożenieOperand iResult nie ma znaczenia dla tego przykładu. Teraz wyobraźmy sobie jakiś kod, który przestrzegaOperands i przyniesie rezultatResults:

void AddNewOperand(Operand operand) {
    try {
        _container.Lock.EnterWriteLock();
        _container.Operands.Add(operand);
    }
    finally {
        _container.ExitReadLock();
    }
}

Nasz hipotetyczny obserwator zrobi coś podobnego, ale pochłonie nowy element i zablokuje sięEnterReadLock() uzyskać operandy, a następnieEnterWriteLock() aby dodać wynik (pozwól mi pominąć ten kod). Spowoduje to wyjątek z powodu rekursji, ale jeśli ustawięLockRecursionPolicy.SupportsRecursion wtedy po prostu otworzę mój kod na martwe blokady (odMSDN):

Domyślnie nowe instancje ReaderWriterLockSlim są tworzone za pomocą flagi LockRecursionPolicy.NoRecursion i nie zezwalają na rekursję. Ta domyślna polityka jest zalecana dla wszystkich nowych opracowań, ponieważrekursja wprowadza niepotrzebne komplikacje iczyni twój kod bardziej podatnym na zakleszczenia.

Powtarzam istotną część dla jasności:

Rekursja [...] czyni twój kod bardziej podatnym na zakleszczenia.

Jeśli się nie mylęLockRecursionPolicy.SupportsRecursion jeśli z tego samego wątku zapytam, powiedzmy, przeczytaj blokadęktoś w innym przypadku prosi o blokadę zapisu, a wtedy będę miał blokadę, a to, co MSDN mówi, ma sens. Ponadto rekursja zbytnio obniży wydajność w wymierny sposób (a nie tego chcę, jeśli używamReadWriterLockSlim zamiastReadWriterLock lubMonitor).

Pytania)

Na koniec moje pytania (proszę zauważyć, że nie szukam dyskusji na temat ogólnych mechanizmów synchronizacji, wiedziałbym, co jest nie takten scenariusz producent / obserwowalny / obserwator):

Co jest lepsze w tej sytuacji? UnikaćReadWriterLockSlim na korzyśćMonitor (nawet jeśli w rzeczywistym świecie czyta się kod znacznie więcej niż pisze)?Zrezygnuj z takiej zgrubnej synchronizacji? Może to nawet zapewnić lepszą wydajność, ale spowoduje, że kod stanie się znacznie bardziej skomplikowany (oczywiście nie w tym przykładzie, ale w rzeczywistym świecie).Czy powinnam po prostu dokonać asynchronicznych powiadomień (z obserwowanej kolekcji)?Coś innego, czego nie widzę?

Wiem, że nie maNajlepiej mechanizm synchronizacji taknarzędzie używamy musi być odpowiedni dla naszego przypadku, ale zastanawiam się, czy istnieją jakieś najlepsze praktyki lub po prostu ignoruję coś bardzo ważnego między wątkami i obserwatorami (wyobraź sobie, że używaszRozszerzenia reaktywne Microsoft ale pytanie jest ogólne, a nie związane z tymi ramami).

Możliwe rozwiązania?

Spróbuję odroczyć wydarzenia (w jakiś sposób):

Pierwsze rozwiązanie
Każda zmiana nie wystrzeliCollectionChanged wydarzenie, jest przechowywane w kolejce. Gdy dostawca (obiekt, który przesyła dane) skończy, ręcznie wymusi opróżnienie kolejki (podnoszenie każdego zdarzenia w sekwencji). Można to zrobić w innym wątku lub nawet w wątku wywołującego (ale poza blokadą).

Może działać, ale sprawi, że wszystko będzie mniej „automatyczne” (każde powiadomienie o zmianie musi zostać uruchomione ręcznie przez samego producenta, więcej kodu do napisania, więcej błędów dookoła).

Drugie rozwiązanie
Innym rozwiązaniem może być podanie odniesienia do naszegozamek do obserwowalnej kolekcji. Jeśli zawijamReadWriterLockSlim w obiekcie niestandardowym (przydatne do ukrycia go w łatwy w użyciuIDisposable obiekt) Mogę dodać aManualResetEvent aby powiadomić, że wszystkie blokady zostały zwolnione w ten sposób, sama kolekcja może powodować zdarzenia (ponownie w tym samym wątku lub w innym wątku).

3. rozwiązanie
Innym pomysłem może być tylko asynchroniczne tworzenie zdarzeń. Jeśli program obsługi zdarzeń będzie potrzebował blokady, zostanie zatrzymany, aby czekać na zmianę czasu. W tym celu martwię się o dużą ilość wątków, które mogą być użyte (zwłaszcza jeśli z puli wątków).

Szczerze mówiąc nie wiem, czy którekolwiek z nich ma zastosowanie w aplikacjach z prawdziwego świata (osobiście - z punktu widzenia użytkowników - wolę drugi, ale implikuje to niestandardową kolekcję dla wszystkiego i sprawia, że ​​kolekcja jest świadoma wątków i uniknęłoby tego możliwy). Nie chciałbym, aby kod był bardziej skomplikowany niż to konieczne.

questionAnswers(3)

yourAnswerToTheQuestion