O strategie mai eficienta cand lucram cu ConcurrentDictionary

ConcurrentDictionary are o trasatura specifica: in unele cazuri poate sa nu se manifeste asa cum ti-ai dorit. Iata un exemplu. Sa presupunem ca trebuie sa facem niste caching astfel incat rezultatele unui calcul sa fie luate din cache, daca sunt acolo, si adaugate cache-ului intr-o maniera transparenta daca cineva a facut o greseala.

Feb 18, 2016 1806
ConcurrentDictionary are o trasatura specifica: in unele cazuri poate sa nu se manifeste asa cum ti-ai dorit. Iata un exemplu. Sa presupunem ca trebuie sa facem niste caching astfel incat rezultatele unui calcul sa fie luate din cache, daca sunt acolo, si adaugate cache-ului intr-o maniera transparenta daca cineva a facut o greseala.

O simpla implementare a unui provider cu un cache aside pattern ar arata asa:

ConcurrentDictionary1.jpg

In termeni de multi-threading, aceasta implementare este corecta. Chiar daca metoda RunOperationOrGetFromCache pentru acelasi operationId a fost invocata din doua threaduri, fiecare va avea acelasi rezultat. Problema este ca, desi rezultatul este acelasi, avem doua operatii care merg in acelasi timp. Rezultatul primei operatii va fi plasat in cache si rezultatul celei de-a doua operatii va fi eliminat!

Motivul pentru care se intampla acest lucru consta in implementarea metodei AddOrGet a clasei ConcurrentDictionary. De fapt, utilizarea lui AddOrGet este echivalenta cu utilizarea consistenta a metodelor precum TryGetValue TryAdd in propriul nostru cod (metoda AddOrGet este un pic mai complicata fata de o simpla invocare a acestor doua metode):

ConcurrentDictionary2.jpg

Acum, ar trebui sa fie clar ca atunci cand doua threaduri vor intra in conflict si vor incerca simultan sa acceseze aceeasi operatie, o operatie pe termen lung va dura de doua ori mai mult. Insa nu este chiar atat de rau. Din moment ce utilizarea metodei AddOrGet va plasa doar primul rezultat in colectie, putem sa folosim urmatorea solutie:

ConcurrentDictionary3.jpg

In loc sa stocam doar rezultatul unei operatii lungi, cacheul va stoca si “lazy shell” – Lazy. In acest caz, cand exista referinte simultane la cache din partea unor threaduri multiple doar un constructor object Lazy<T> va fi invocat de cateva ori si operatia in sine va fi rulata doar o data – cand se acceseaza proprietatea Value!

Sergey Teplyakov
Expert in .Net, С++ and Application Architecture

Daca iti place acest articol, distribuie-l si prietenilor tai!




Mai ai intrebari?
Contacteaza-ne.
Thank you.
Your request has been received.