Właśnie zdałem sobie sprawę, że w jakimś miejscu w moim kodzie instrukcja return znajduje się wewnątrz zamka, a czasem na zewnątrz. Która jest najlepsza?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
Którego powinienem użyć?
c#
.net
multithreading
mutex
Patrick Desjardins
źródło
źródło
Odpowiedzi:
Zasadniczo, co kiedykolwiek czyni kod prostszym. Pojedynczy punkt wyjścia jest fajnym ideałem, ale nie wyginałbym kodu z kształtu tylko po to, aby to osiągnąć ... A jeśli alternatywą jest zadeklarowanie zmiennej lokalnej (poza zamkiem), zainicjowanie jej (wewnątrz zamka) i potem zwracając go (poza zamek), wtedy powiedziałbym, że proste „return foo” wewnątrz zamka jest o wiele prostsze.
Aby pokazać różnicę w IL, zakodujmy:
(zauważ, że z przyjemnością twierdzę, że
ReturnInside
jest to prostszy / czystszy kawałek C #)Spójrz na IL (tryb zwolnienia itp.):
Więc na poziomie IL są one [podaj lub przyjmij kilka nazw] identyczne (nauczyłem się czegoś ;-p). W związku z tym jedynym sensownym porównaniem jest (wysoce subiektywne) prawo lokalnego stylu kodowania ... Wolę
ReturnInside
prostotę, ale też nie byłbym podekscytowany.źródło
ret
wejść do.try
regionu.Nie robi to żadnej różnicy; oba są tłumaczone na to samo przez kompilator.
Aby wyjaśnić, jedno z nich jest skutecznie tłumaczone na coś z następującą semantyką:
źródło
Zdecydowanie umieściłbym powrót w zamku. W przeciwnym razie ryzykujesz, że inny wątek wejdzie do blokady i zmodyfikuje zmienną przed instrukcją return, co spowoduje, że oryginalny obiekt wywołujący otrzyma inną wartość niż oczekiwano.
źródło
To zależy,
Mam zamiar iść pod prąd. Generalnie wracałbym do wnętrza zamka.
Zwykle zmienna mydata jest zmienną lokalną. Lubię deklarować zmienne lokalne podczas ich inicjalizacji. Rzadko mam dane do zainicjowania mojej wartości zwracanej poza blokadą.
Więc twoje porównanie jest błędne. Chociaż idealnie byłoby, gdyby różnica między tymi dwiema opcjami była taka, jak napisałeś, co wydaje się być ukłonem w stronę przypadku 1, w praktyce jest trochę brzydsza.
vs.
Uważam, że przypadek 2 jest znacznie łatwiejszy do odczytania i trudniejszy do zepsucia, szczególnie w przypadku krótkich fragmentów.
źródło
Jeśli uważasz, że zamek na zewnątrz wygląda lepiej, ale bądź ostrożny, jeśli w końcu zmienisz kod na:
Jeśli f () musi zostać wywołane z trzymaną blokadą, to oczywiście musi znajdować się wewnątrz zamka, ponieważ takie utrzymywanie zwrotów wewnątrz zamka ma sens.
źródło
Na co warto zwrócić uwagę, dokumentacja na MSDN zawiera przykład powrotu z wnętrza zamka. Z innych odpowiedzi tutaj, wydaje się, że jest dość podobny do IL, ale wydaje mi się, że bezpieczniej jest wrócić z wnętrza blokady, ponieważ wtedy nie ryzykujesz, że zmienna zwracana zostanie nadpisana przez inny wątek.
źródło
Aby ułatwić innym programistom czytanie kodu, sugerowałbym pierwszą alternatywę.
źródło
lock() return <expression>
oświadczenia zawsze:1) wprowadź zamek
2) tworzy lokalny (bezpieczny wątkowo) magazyn wartości określonego typu,
3) wypełnia sklep wartością zwracaną przez
<expression>
,4) zamek wyjściowy
5) zwrócić sklep.
Oznacza to, że wartość, zwrócona z instrukcji lock, jest zawsze „gotowana” przed powrotem.
Nie martw się
lock() return
, nie słuchaj tutaj nikogo))źródło
Uwaga: uważam, że ta odpowiedź jest zgodna z faktami i mam nadzieję, że jest również pomocna, ale zawsze chętnie ją poprawiam na podstawie konkretnych opinii.
Podsumowując i uzupełniając istniejące odpowiedzi:
W Zaakceptowanych odpowiedź pokazuje, że, którego składnia forma niezależnie wybierzesz w C # kodu w kodzie IL - a więc w czasie wykonywania - w
return
nie zdarza się aż po zwolnieniu blokady.return
wewnątrzlock
blok zatem ściśle mówiąc, wprowadza w błąd regulacji przepływu [1] , jest składniowo dogodne tym, że eliminuje potrzebę przechowywania wartość powrotną w AUX. zmienna lokalna (zadeklarowana poza blokiem, aby można jej było używać zreturn
poza blokiem) - patrz odpowiedź Edwarda KMETT-a .Osobno - i ten aspekt jest przypadkowy w stosunku do pytania, ale wciąż może być interesujący (odpowiedź Ricardo Villamila próbuje go rozwiązać, ale myślę, że błędnie) - łącząc
lock
stwierdzenie zereturn
stwierdzeniem - tj. Uzyskanie wartościreturn
w bloku chronionym przed współbieżny dostęp - tylko sensownie „chroni” zwróconą wartość w zakresie wywołującego , jeśli faktycznie nie wymaga ochrony po uzyskaniu , co ma zastosowanie w następujących scenariuszach:Jeśli zwracana wartość jest elementem z kolekcji, który wymaga tylko ochrony w zakresie dodawania i usuwania elementów, a nie modyfikacji samych elementów i / lub ...
... jeśli zwracana wartość jest instancją typu wartości lub łańcuchem .
W każdym innym przypadku blokowanie musi zostać wykonane przez obiekt wywołujący , a nie (tylko) wewnątrz metody.
[1] Theodor Zoulias wskazuje, że jest to technicznie również do umieszczania
return
wewnątrztry
,catch
,using
,if
,while
,for
, ... wypowiedzi; jednakże jest to szczególny cellock
oświadczenia, który prawdopodobnie zachęci do zbadania prawdziwego przepływu kontroli, czego dowodem jest to, że zadano to pytanie i otrzymano wiele uwagi.[2] Dostęp do instancji typu wartościowego niezmiennie tworzy kopię lokalną wątku na stosie; mimo że ciągi są technicznie instancjami typu referencyjnego, skutecznie zachowują się jak wystąpienia typu wartości.
źródło
lock
instrukcji zwrotu i wyprowadzasz jej znaczenie z umieszczenia instrukcji zwrotu. Która jest dyskusją niezwiązaną z tym pytaniem IMHO. Uważam również, że użycie „przekłamań” jest dość niepokojące. Jeśli powrocie zlock
wprowadza w błąd regulacji przepływu, to samo można powiedzieć o powrocie ztry
,catch
,using
,if
,while
,for
, i inne konstrukt języka. To tak, jakby powiedzieć, że język C # jest pełen błędnych reprezentacji przepływu sterowania. Jezu ...try
,if
... Ja osobiście nie wydają się nawet myśleć o tym, ale w kontekścielock
, w szczególności, pojawiło się pytanie dla mnie - a jeśli nie wynikają dla innych też to pytanie nigdy nie zostały poproszone , a zaakceptowana odpowiedź nie posunęłaby się zbytnio, aby zbadać prawdziwe zachowanie.