Megerősítés needed [C#]

Fórumok

Írtam egy active record / ORM rendszert. Ennek a rendszernek - amennyire lehet - függetlennek kell lennie a felhasználótól, viszont sokszor igényel(het) adatbázis kapcsolatot, és nem csak egy helyről. Ha mindenhol új kapcsolatot nyitok, az iszonyatosan sok időbe telhet, ezért elhatároztam, hogy statikus kapcsolatot fogok használni.

A problémám:

Weben minden kérés külön szálon érkezik. Ez persze asztali alkalmazásnál is előfordulhat. Hogy thread safe legyen, azt találtam ki, hogy minden szálhoz rendelek egy kapcsolatot, egy Dictionary<Thread, OdbcConnection> -nel, ami igény esetén - ha még nem létezik a szálhoz kapcsolat - automatán példányosítja és megnyitja azt, majd visszaaadja. Ha már létezik, akkor csak visszaadja. A Thread "azonosítása" a Thread.CurrentThread által történik.

Persze ezt meg is kell semmisíteni minden esetben, erre pedig azt gondoltam ki, hogy csináltam egy osztályt, ami IDisposable, és csak annyit tesz, hogy a Dispose metódusa a statikus, adott szálhoz rendelt adatbáziskapcsolatot Dispose-olja, és kiveszi a Dictionary<Thread, OdbcConnection>-ből.

Ezt utána a következőképpen lehet használni (ha a statikus Osztaly.Kapcsolat egy új példányt ad vissza ebből az IDisposable osztályból):


using(var conn = Osztaly.Kapcsolat)
{
    // ezen a szálon lehet mindenfélét csinálni, lesz hozzá kapcsolat
}
// itt pedig mindenképp meg lesz szüntetve a kapcsolat

Kérdések: helyesen járok-e el? Biztosan biztonságos ez, vagy történhetnek váratlan dolgok? Mennyire antipattern ez a megoldás?

Hozzászólások

Poolozd a kapcsolatokat, hozz letre N darabot, rakd oket egy taroloba es innen "kolcsonozz" kapcsolatot, amikor szukseged van ra. Hasznalatban levo kapcsolathoz mas thread meg nem nyul hozza, amig vissza nem adod a poolnak.

Feltetelezem, hogy tanulasi cellal talalod fel a kereket, masert nemigen van ertelme...

----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"

Tanulmányi célból készült, de később is szeretném felhasználni. Ezt a connection poolingot megtaláltam már, de:
- nem tudom hogy ha nem saját szerveren megy a dolog, akkor lehetséges-e (nem férek hozzá a beállításaihoz). Amit eddig találtam, ott registryben kellett turkálni. Vagy ezt teljesen saját implementációval kellene megoldani?
- MySQL-hez készült, szintén a fentebb említett okokból ODBC connectorral, mivel a Connector/NET-et tiltja a biztonsági szint. Azzal hogy működik ez?
- nem biztos hogy megoldás a problémámra :)

Lehet, hogy ez az egész egy nagy tervezési hiba, de azt találtam ki, hogy mondjuk definiálok egy User osztályt. Annak a mezőire beállítja a felhasználót lekérő adatbázis eredmény mezőit. Ha definiálok egy Post objektumot, aminek van egy szerzője, akkor egy eseménykezelőben, miután a lekérdezés megtörtént (tehát nem join-nal), le tudja kérni a megfelelő User-t, egy belső User objektumként.

A probléma ott kezdődött, hogy ha mondjuk van 1000 db Post, amiben van 2 db ilyen belső objektum, és én mindegyiknek külön kapcsolatot adok, akkor mivel minden belső lekérdezés független a külsőtől, 1+2*1000 db kapcsolat kell (minden Post objektumhoz 2 további lekérdezés tartozik), pedig elég lenne összesen 1 db is. Az gondolom érzékelhető, hogy ha 1-1 sor lekéréséhez (PK alapján) külön kapcsolatokat hozok létre, akkor a kapcsolódás és az objektumok létrehozása több időbe fog telni mint a lekérések. (Persze ezt jobb join-nal, és sima DataReader-rel megoldani.)

Ha a felhasználóra bízom hogy adjon 1 db kapcsolatot, akkor azt mindenhol keringetni kell a rendszerben, ami így elég bonyolultan összefüggővé válik. Ezért jobb lenne, ha 1 db kapcsolattal szolgálna ki 1 db embert, és kész. Ilyenkor viszont szálanként kell kezelni, és biztosítani is kell hogy ne maradjon meg örökké a statikus kapcsolat.

Szóval ezzel küzdöttem eddig, mert ha ezt meg tudom oldani, akkor a többi része eléggé tetszik nekem.

Még egy kérdésem felmerült az előbb: A Dictionary, ha a kulcsai a szálak, és még a szál futása alatt eltávolításra kerül az elem, akkor biztonságos? Mert van ConcurrentDictionary is.

Kapcsolatok menedzsmentje semmikepp sem az ActiveRecord implementadio feladata, annak csak keregetnie kene a kapcsolatokat egy kulso DataSource-felesegbol. .NET-hez nem ertek, ezert sajnos konkretumokkal nem tudok szolgalni.

de később is szeretném felhasználni

Szerintem ne szivasd magad, hacsak nem alkotsz valami igazan zsenialisat (kerdeseid alapjan ezt nem latom annyira valoszinunek a kozeljovoben :^)), a te implementaciodnak nem lesznek elonyei a mar elerheto 3rd party cuccokhoz kepest, es nem is lesz annyira stabil (a 3rd party ORM libeket ugye tobben hasznaljak es tesztelgetik...)!

----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"

Van ennek alapja, egy létező rendszer, amit PHP-ban írtak (a CodeIgniter AR implementációja nagyon megtetszett).

Én szerettem volna ugyanazt .NET-ben használni, de még hasonló elvűt sem találtam, ezért írtam sajátot. Csak itt ugye nem olyan egyszerűek a dolgok mint ott, nem elég a szkript elején egy mysql_connect(), a végén meg úgyis mindegy, de inkább zárjuk be, hanem oda kell figyelni a kapcsolatokra, ráadásul a statikus változók a szerveren mindenkinek megosztottak. És mivel a C# erősen típusos, más lehetőségek vannak, mint PHP-ban.

Nem. Nem úgy működnek ahogy szeretném. Ez már a readme-ből is ki tud derülni.

A komolyabb dolgokkal kapcsolatban az az első véleményem, hogy én SQL nyelven szeretném használni, nem HQL vagy LINQ nyelven. Nem akarom leírni az összes kapcsolatot XML fájlokba, nem akarok automata összerendelést az Orders és az Order között, nem akarok 3 táblához 1500 sor generált kódot, nem akarok semmilyen bonyolítást, csak annyit szeretnék, hogy ha azt mondom, hogy Rendszerem.Insert("users", this), akkor szúrja be a users táblámba az objektumomat, és hasonló dolgok.

Gyári Entity model framework miért nem jó?

--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.

Az Add-hoz és a Remove-hoz mindenképp lock kell a Dictionary-re, olvasni bátran szabad.

Hm. Próbálgattam sokat, gyakorlatban működött ez a változat. Ezt írják egyébként:

"A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization."

Rosszul értem?

Azt írja hogy az olvasás threadsafe, az írás nem az. Minden írással (módosítással) kapcsolatos művelethez lockolok. Bonyolítja a helyzetet, hogy a Dictionary belülről egy HashTable. Ez az elemek kiválasztásának menetét nagyban befolyásolja, mert így nem kell enumerálni. A SortedDictionary-vel például ugyanez nem működik, az leáll a tesztprogramommal is.

Logikusnak hangzik, csak ez az egész olyan furcsán túl jól működik.

Ha nem lockolok az írásra, hamar elszáll (out of bounds és egyebek, tehát valaki módosítja épp a gyűjteményt). Az elképzelhetetlen, hogy mivel a kiválasztás O(1)-es, és egyedi hash alapján történik, akkor is helyes eredményt ad, ha egyvalaki közben módosítja? Főleg ha ki van zárva hogy azt keressük amit épp valaki módosít?

Szerintem végtelenítem és futva hagyom éjszakára, kíváncsiságból.

A kollekciot.

Egesz ejjel futott a cucc (es meg mindig fut), hiba nelkul.

Szerintem ez a hash-sel fugghet ossze. Talan amig egyedileg megtalal mindent hash alapjan addig nem gond ha tobb az olvaso es van max egy iro, mivel nincs enumeracio. Ha utkozesfeloldas lenne, akkor lenne baj.

Semmit nem bizonyit a teszted, csak azt, hogy nalad ugy jottek ki az idozitesek, hogy nem lett belole baj. Masik gepen barmikor elhasalhat.

Nem hiszem, hogy olyan bonyolult lenne megerteni, hogy senki nem garantalja, hogy mikozben az olvaso eppen a kollekcio adatstrukturait olvassa, az iro nem fogja eppen pont azokat a strukturakat atirni. Egyreszt nagyon nagy hibat kovetsz el, ha igy hasznalod, masreszt a helyedben elgondolkoznek ezen, mert ha neked ez komolyan kerdes, akkor nem sajat ActiveRecord rendszert kellene irnod, hanem vegigragni az alap parhuzamossagi problemakat. No offense, konstruktiv kritikanak szanom.

----------------------
while (!sleep) sheep++;

Igazad van, valóban végig kell rágnom őket. Épp annak az elején járok egyébként. Csak nehéz ehhez segítséget találnom.

Nem használnám így a dolgot jelen helyzetben. Ha tudnám hogy igaz vagy nem amit mondok, akkor használnám vagy nem, de így, hogy favágó módszerrel tesztelem, nem fogom élesben használni semmiképp. Amit írtam az csak egy ötlet volt, hogy miért nem hasalt el (még mindig fut egyébként). Tehát feltételeztem emellett azt is, hogy ha két hash megegyezne, akkor már történhetne baj.

Sose kerültem még ilyen problémával szembe, hogy - ráadásul szerveren - statikus erőforrást akartam megosztani szálak között, minél kisebb lock-kal. Kölcsönös kizáráshoz (élesben) meg termelő-fogyasztóhoz (próbaképp) volt már szerencsém, de ez a mostani dolog nem egyszerű kérdés, ha garantáltnak kell lennie a helyes működésének, és jó volna ha gyors is lenne. Arról nem is beszélve, hogy a szál fölött, amihez az erőforrást biztosítani akarom, semmilyen hatalmam sincs, ráadásul a threadpool-ban van, ezért még az is esetleges, hogy leáll-e valaha.

A lock is egyfajta mutex, nem? Ami a párhuzamos végrehajtást zárja ki a kód megjelölt részein. Egyéb mutexek pedig más dolgokat zárnak ki, például szemaforokkal valamilyen közös erőforrást védenek adott ideig vagy adott esemény bekövetkezéséig (pl. kapcsolat vagy file bezárása).

Tehát itt a kód: http://hup.pastebin.com/9iVtWUna

Cseréld le SortedDictionary-re, és már nem megy. Ahhoz az egész Add metódust lock-olni kell. Nekem nyilván célom minél kisebb lock-ot csinálni.

Szerk.: egy ilyen teszt elég favágó módszer, úgyhogy továbbra is megerősítés needed.
Szerk2: az if (!dict.ContainsKey(Thread.CurrentThread)) ellenőrzés fölösleges, csak az eredeti algoritmusból indultam ki és benne maradt

Az, hogy neked mukodott, nem jelent semmit.

> A Dictionary can support multiple readers concurrently, as long as the collection is not modified

Errol beszelek. Ha van writer, akkor readereket is lockolni kell. Semmit nem er, ha csak a writereket lockolod.

----------------------
while (!sleep) sheep++;

ReaderWriterLockSlim-nek nézz utána. Ha csak olvasási lock van felvéve, gyakorlatilag nem blokkol, ha van írási is, akkor lock-ként működik. Nagyvonalakban. Cserébe kb 1,7-szer lassabb a locknál.

Szerk.: hulyeseget irtam, azt hittem, hogy a sima RWLockhoz hasonlitod.

andrasf: hasznalhatsz RWLS-t, viszont _mindig_ lockolj, olvasasnal is, csak akkor read lockot kerj. Viszont vigyazz az ehezesre (sok reader eseten a writerek esetleg nem jutnak hozza az eroforrashoz).

----------------------
while (!sleep) sheep++;