Mam kilka właściwości, na których mam zamiar używać blokad odczytu / zapisu. Mogę je zaimplementować za pomocą klauzuli try finally
lub using
klauzuli.
W przypadku try finally
zdobycia blokady przed try
i zwolnienia w finally
. W using
klauzuli utworzyłbym klasę, która uzyskuje blokadę w swoim konstruktorze i zwalnia w swojej metodzie Dispose.
Używam blokad odczytu / zapisu w wielu miejscach, więc szukałem sposobów, które mogą być bardziej zwięzłe niż try finally
. Interesuje mnie kilka pomysłów, dlaczego jeden sposób może nie być zalecany lub dlaczego jeden może być lepszy od drugiego.
Metoda 1 ( try finally
):
static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
get
{
rwlMyLock_m .AcquireReaderLock(0);
try
{
return dtMyDateTime_m
}
finally
{
rwlMyLock_m .ReleaseReaderLock();
}
}
set
{
rwlMyLock_m .AcquireWriterLock(0);
try
{
dtMyDateTime_m = value;
}
finally
{
rwlMyLock_m .ReleaseWriterLock();
}
}
}
Metoda 2:
static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
get
{
using (new ReadLock(rwlMyLock_m))
{
return dtMyDateTime_m;
}
}
set
{
using (new WriteLock(rwlMyLock_m))
{
dtMyDateTime_m = value;
}
}
}
public class ReadLock : IDisposable
{
private ReaderWriterLock rwl;
public ReadLock(ReaderWriterLock rwl)
{
this.rwl = rwl;
rwl.AcquireReaderLock(0);
}
public void Dispose()
{
rwl.ReleaseReaderLock();
}
}
public class WriteLock : IDisposable
{
private ReaderWriterLock rwl;
public WriteLock(ReaderWriterLock rwl)
{
this.rwl = rwl;
rwl.AcquireWriterLock(0);
}
public void Dispose()
{
rwl.ReleaseWriterLock();
}
}
c#
.net
multithreading
using-statement
Jeremy
źródło
źródło
Odpowiedzi:
Z MSDN przy użyciu instrukcji (odwołanie w C #)
{ Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } }
Zasadniczo jest to ten sam kod, ale z ładnym automatycznym sprawdzaniem wartości null i dodatkowym zakresem dla twojej zmiennej . Dokumentacja stwierdza również, że „zapewnia poprawne użycie obiektu IDisposable”, więc równie dobrze możesz uzyskać jeszcze lepszą obsługę frameworka dla wszelkich niejasnych przypadków w przyszłości.
Więc wybierz opcję 2.
Umieszczenie zmiennej w zakresie, który kończy się natychmiast po tym, jak nie jest już potrzebna, jest również plusem.
źródło
using
ten przypadek? nie do tegousing
służy.try/catch
ponieważ wygląda to na jedyny sposób obsługi przeztry/catch/finally
blok.using
try/finally
, to chyba jedyna opcja w tym przypadku. IMO, jednak myślę, że zawsze powinien istnieć jakiś obiekt / fragment kodu odpowiedzialny za utrzymanie czasu życia obiektu w takich przypadkach (gdzie Dispose () powinno być zawsze wywoływane.) Jeśli jedna klasa obsługuje tylko instancję i ktoś inny musi pamiętać żeby się go pozbyć, wydaje mi się, że trochę tam pachnie. Nie wiem, jak można to dodać na poziomie języka.Zdecydowanie wolę drugą metodę. Jest bardziej zwięzły w miejscu użycia i mniej podatny na błędy.
W pierwszym przypadku osoba edytująca kod musi uważać, aby nie wstawić niczego między wywołaniem Acquire (Read | Write) Lock a próbą.
(Stosowanie blokady odczytu / zapisu na poszczególnych właściwościach, takich jak ta, jest jednak zwykle przesadą. Najlepiej jest je stosować na znacznie wyższym poziomie. Zwykła blokada jest tutaj często wystarczająca, ponieważ możliwość rywalizacji jest prawdopodobnie bardzo mała, biorąc pod uwagę czas utrzymywania blokady dla, a uzyskanie blokady odczytu / zapisu jest operacją droższą niż zwykła blokada).
źródło
Rozważ możliwość, że oba rozwiązania są złe, ponieważ maskują wyjątki .
A
try
bez acatch
powinno być oczywiście złym pomysłem; zobacz MSDN, aby dowiedzieć się, dlaczego tousing
stwierdzenie jest również niebezpieczne.Należy również zauważyć, że firma Microsoft zaleca teraz ReaderWriterLockSlim zamiast ReaderWriterLock.
Na koniec zwróć uwagę, że przykłady firmy Microsoft używają dwóch bloków try-catch, aby uniknąć tych problemów, np
try { try { //Reader-writer lock stuff } finally { //Release lock } } catch(Exception ex) { //Do something with exception }
Proste, spójne i czyste rozwiązanie to dobry cel, ale zakładając, że nie możesz po prostu użyć
lock(this){return mydateetc;}
, możesz ponownie rozważyć podejście; z więcej informacji Jestem pewien, że Stack Overflow może pomóc ;-)źródło
Osobiście używam instrukcji C # „using” tak często, jak to możliwe, ale jest kilka konkretnych rzeczy, które robię razem z nią, aby uniknąć wymienionych potencjalnych problemów. Ilustrować:
void doSomething() { using (CustomResource aResource = new CustomResource()) { using (CustomThingy aThingy = new CustomThingy(aResource)) { doSomething(aThingy); } } } void doSomething(CustomThingy theThingy) { try { // play with theThingy, which might result in exceptions } catch (SomeException aException) { // resolve aException somehow } }
Zauważ, że oddzielam instrukcję „using” na jedną metodę, a użycie obiektu (obiektów) na inną metodę za pomocą bloku „try” / „catch”. Mogę zagnieżdżać kilka takich instrukcji „using” dla powiązanych obiektów (czasami w kodzie produkcyjnym zagnieżdżam się trzy lub cztery).
W moich
Dispose()
metodach dla tych niestandardowychIDisposable
klas łapię wyjątki (ale NIE błędy) i rejestruję je (za pomocą Log4net). Nigdy nie spotkałem się z sytuacją, w której którykolwiek z tych wyjątków mógłby wpłynąć na moje przetwarzanie. Potencjalne błędy, jak zwykle, mogą rozprzestrzeniać się w górę stosu wywołań i zazwyczaj kończą przetwarzanie z zarejestrowanym odpowiednim komunikatem (błąd i ślad stosu).Gdybym w jakiś sposób napotkał sytuację, w której może wystąpić znaczący wyjątek podczas
Dispose()
, przeprojektowałbym tę sytuację. Szczerze mówiąc, wątpię, żeby to kiedykolwiek się stało.W międzyczasie zakres i zalety „używania” czyszczenia sprawiają, że jest to jedna z moich ulubionych funkcji C #. Nawiasem mówiąc, pracuję w Javie, C # i Pythonie jako moich podstawowych językach, z wieloma innymi wrzucanymi tu i ówdzie, a „używanie” jest jedną z moich ulubionych funkcji językowych, ponieważ jest to praktyczny, codzienny koń .
źródło
Podoba mi się trzecia opcja
private object _myDateTimeLock = new object(); private DateTime _myDateTime; public DateTime MyDateTime{ get{ lock(_myDateTimeLock){return _myDateTime;} } set{ lock(_myDateTimeLock){_myDateTime = value;} } }
Z dwóch opcji, druga opcja jest najczystsza i łatwiejsza do zrozumienia, co się dzieje.
źródło
„Wiązka właściwości” i blokowanie na poziomie metody pobierającej i ustawiającej właściwości wygląda nieprawidłowo. Twoje blokowanie jest zbyt drobnoziarniste. W większości typowych zastosowań obiektów chciałbyś się upewnić, że nabyłeś blokadę, aby uzyskać dostęp do więcej niż jednej właściwości w tym samym czasie. Twój konkretny przypadek może być inny, ale trochę w to wątpię.
W każdym razie, uzyskanie blokady podczas uzyskiwania dostępu do obiektu zamiast do właściwości znacznie zmniejszy ilość kodu blokującego, który trzeba będzie napisać.
źródło
DRY mówi: drugie rozwiązanie. Pierwsze rozwiązanie powiela logikę używania zamka, podczas gdy drugie nie.
źródło
Bloki Try / Catch służą zazwyczaj do obsługi wyjątków, natomiast bloki służą do zapewnienia, że obiekt zostanie usunięty.
W przypadku blokady odczytu / zapisu try / catch może być najbardziej przydatne, ale możesz również użyć obu, na przykład:
using (obj) { try { } catch { } }
dzięki czemu można niejawnie wywołać swój interfejs IDisposable, a także zwięźle obsługiwać wyjątki.
źródło
Myślę, że metoda 2 byłaby lepsza.
źródło
Chociaż zgadzam się z wieloma powyższymi komentarzami, w tym szczegółowością blokady i wątpliwą obsługą wyjątków, pytanie dotyczy podejścia. Pozwólcie, że podam jeden duży powód, dla którego wolę używać zamiast try {} wreszcie modelu… abstrakcji.
Mam model bardzo podobny do twojego z jednym wyjątkiem. Zdefiniowałem interfejs bazowy ILock i umieściłem w nim jedną metodę o nazwie Acquire (). Metoda Acquire () zwróciła obiekt IDisposable iw rezultacie oznacza, że tak długo, jak obiekt, z którym mam do czynienia, jest typu ILock, można go używać do blokowania zakresu. Dlaczego to jest ważne?
Mamy do czynienia z wieloma różnymi mechanizmami blokowania i zachowaniami. Twój obiekt blokady może mieć określony limit czasu. Implementacją blokady może być blokada monitora, blokada czytnika, blokada programu zapisującego lub blokada obrotu. Jednak z punktu widzenia dzwoniącego to wszystko nie ma znaczenia, to, na czym mu zależy, to to, że umowa na blokowanie zasobu jest honorowana, a blokada robi to w sposób zgodny z jego implementacją.
interface ILock { IDisposable Acquire(); } class MonitorLock : ILock { IDisposable Acquire() { ... acquire the lock for real ... } }
Podoba mi się twój model, ale rozważę ukrycie mechaniki zamka przed dzwoniącym. FWIW, zmierzyłem narzut związany z użyciem techniki w porównaniu z próbą ostateczną, a narzut związany z przydzieleniem jednorazowego obiektu będzie miał między 2-3% narzut wydajności.
źródło
Dziwię się, że nikt nie zasugerował umieszczenia próby w końcu w anonimowych funkcjach. Podobnie jak technika tworzenia instancji i usuwania klas za pomocą instrukcji using, utrzymuje to blokadę w jednym miejscu. Sam wolę to tylko dlatego, że wolę przeczytać słowo „w końcu” niż słowo „pozbyć się”, kiedy myślę o zwolnieniu blokady.
class StackOTest { private delegate DateTime ReadLockMethod(); private delegate void WriteLockMethod(); static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); private DateTime dtMyDateTime_m; public DateTime MyDateTime { get { return ReadLockedMethod( rwlMyLock_m, delegate () { return dtMyDateTime_m; } ); } set { WriteLockedMethod( rwlMyLock_m, delegate () { dtMyDateTime_m = value; } ); } } private static DateTime ReadLockedMethod( ReaderWriterLock rwl, ReadLockMethod method ) { rwl.AcquireReaderLock(0); try { return method(); } finally { rwl.ReleaseReaderLock(); } } private static void WriteLockedMethod( ReaderWriterLock rwl, WriteLockMethod method ) { rwl.AcquireWriterLock(0); try { method(); } finally { rwl.ReleaseWriterLock(); } } }
źródło
SoftwareJedi, nie mam konta, więc nie mogę edytować swoich odpowiedzi.
W każdym razie poprzednia wersja nie była zbyt dobra do użytku ogólnego, ponieważ blokada odczytu zawsze wymagała zwrócenia wartości. To naprawia, że:
class StackOTest { static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); private DateTime dtMyDateTime_m; public DateTime MyDateTime { get { DateTime retval = default(DateTime); ReadLockedMethod( delegate () { retval = dtMyDateTime_m; } ); return retval; } set { WriteLockedMethod( delegate () { dtMyDateTime_m = value; } ); } } private void ReadLockedMethod(Action method) { rwlMyLock_m.AcquireReaderLock(0); try { method(); } finally { rwlMyLock_m.ReleaseReaderLock(); } } private void WriteLockedMethod(Action method) { rwlMyLock_m.AcquireWriterLock(0); try { method(); } finally { rwlMyLock_m.ReleaseWriterLock(); } } }
źródło
Poniższe tworzy metody rozszerzające dla klasy ReaderWriterLockSlim, które umożliwiają wykonanie następujących czynności:
var rwlock = new ReaderWriterLockSlim(); using (var l = rwlock.ReadLock()) { // read data } using (var l = rwlock.WriteLock()) { // write data }
Oto kod:
static class ReaderWriterLockExtensions() { /// <summary> /// Allows you to enter and exit a read lock with a using statement /// </summary> /// <param name="readerWriterLockSlim">The lock</param> /// <returns>A new object that will ExitReadLock on dispose</returns> public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim) { // Enter the read lock readerWriterLockSlim.EnterReadLock(); // Setup the ExitReadLock to be called at the end of the using block return new OnDispose(() => readerWriterLockSlim.ExitReadLock()); } /// <summary> /// Allows you to enter and exit a write lock with a using statement /// </summary> /// <param name="readerWriterLockSlim">The lock</param> /// <returns>A new object that will ExitWriteLock on dispose</returns> public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock) { // Enter the write lock rwlock.EnterWriteLock(); // Setup the ExitWriteLock to be called at the end of the using block return new OnDispose(() => rwlock.ExitWriteLock()); } } /// <summary> /// Calls the finished action on dispose. For use with a using statement. /// </summary> public class OnDispose : IDisposable { Action _finished; public OnDispose(Action finished) { _finished = finished; } public void Dispose() { _finished(); } }
źródło
Właściwie w swoim pierwszym przykładzie, aby zapewnić porównywalność rozwiązań, również byś je
IDisposable
tam wdrożył . Wtedy zadzwoniłbyśDispose()
zfinally
bloku zamiast bezpośrednio zwalniać blokadę.Wtedy byłaby to implementacja typu „jabłka na jabłka” (i MSIL) -wsparcie (MSIL będzie taki sam dla obu rozwiązań). Nadal prawdopodobnie dobrym pomysłem jest użycie
using
ze względu na dodany zakres i ponieważ Framework zapewni właściwe użycieIDisposable
(to drugie jest mniej korzystne, jeśli wdrażaszIDisposable
samodzielnie).źródło
Głupi ja. Można to jeszcze uprościć, czyniąc zablokowane metody częścią każdej instancji (zamiast statycznej, jak w moim poprzednim poście). Teraz naprawdę wolę to, ponieważ nie ma potrzeby przekazywania `rwlMyLock_m 'do innej klasy lub metody.
class StackOTest { private delegate DateTime ReadLockMethod(); private delegate void WriteLockMethod(); static ReaderWriterLock rwlMyLock_m = new ReaderWriterLock(); private DateTime dtMyDateTime_m; public DateTime MyDateTime { get { return ReadLockedMethod( delegate () { return dtMyDateTime_m; } ); } set { WriteLockedMethod( delegate () { dtMyDateTime_m = value; } ); } } private DateTime ReadLockedMethod(ReadLockMethod method) { rwlMyLock_m.AcquireReaderLock(0); try { return method(); } finally { rwlMyLock_m.ReleaseReaderLock(); } } private void WriteLockedMethod(WriteLockMethod method) { rwlMyLock_m.AcquireWriterLock(0); try { method(); } finally { rwlMyLock_m.ReleaseWriterLock(); } } }
źródło