Zamknij i wyrzuć - do kogo zadzwonić?


Odpowiedzi:


191

Chcę wyjaśnić tę sytuację.

Zgodnie z wytycznymi firmy Microsoft dobrą praktyką jest podawanie Closeodpowiedniej metody. Oto cytat z wytycznych projektowych Framework

Rozważ podanie metody Close()oprócz Dispose()standardowej terminologii w tej dziedzinie. Robiąc to, ważne jest, aby Closeimplementacja była identyczna z Dispose...

W większości przypadków Closei Disposemetody są równoważne. Główną różnicą pomiędzy Closei Disposew przypadku SqlConnectionObjectwynosi:

Aplikacja może wywołać Closewięcej niż jeden raz. Nie jest generowany żaden wyjątek.

Jeśli wywołałeś Disposemetodę, SqlConnectionstan obiektu zostanie zresetowany. Jeśli spróbujesz wywołać jakąkolwiek metodę na usuniętym SqlConnection obiekcie, otrzymasz wyjątek.

To mówi:

  • Jeśli używasz obiektu połączenia jeden raz, użyj Dispose.
  • Jeśli obiekt połączenia musi być ponownie użyty, użyj Closemetody.

5
@Chris, dokumentacja dla Close () mówi: „Następnie zwalnia połączenie z pulą połączeń lub zamyka połączenie, jeśli pula połączeń jest wyłączona”. So Close () powinno wystarczyć, aby zapobiec przepełnieniu puli połączeń.
David Hammond,

@DavidHammond: Masz rację. Usuwam mój poprzedni komentarz.
NotMe

3
Czy .Dispose () również zwalnia połączenie z powrotem do puli?
oscilatingcretin

To najlepszy argument, jaki przeczytałem na ten temat w ten czy inny sposób od dekady. Doskonała uwaga.
Michael Erickson

1
Więc to działa w ten sposób 1. con.Open() con.Close(); 2 con.Open(); // reuse 3. con.Dispose(); // use one time con.Open(); // error
shaijut

24

Jak zwykle odpowiedź brzmi: to zależy. Różne klasy są wdrażane IDisposablena różne sposoby i to od Ciebie zależy, czy przeprowadzisz niezbędne badania.

Jeśli chodzi o SqlClientto, zalecaną praktyką jest wykonanie następujących czynności:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

Państwo powinno być wywołanie Dispose(lub Close*) w związku! Czy nie czekać na śmieciarza, aby oczyścić swoje połączenie, to będzie związać się połączenia w basenie aż do następnego cyklu GC (przynajmniej). Jeśli wywołujesz Dispose, nie jest konieczne wywołanie Close, a ponieważ usingkonstrukcja sprawia, że ​​jest tak łatwa w obsłudze Dispose, tak naprawdę nie ma powodu, aby dzwonić Close.

Połączenia są automatycznie umieszczane w puli, a wywołanie Dispose/ Closenawiązanie połączenia nie powoduje fizycznego zamknięcia połączenia (w normalnych okolicznościach). Nie próbuj wdrażać własnego poolingu. SqlClientwykonuje czyszczenie połączenia, gdy jest ono pobierane z puli (na przykład przywraca kontekst bazy danych i opcje połączenia).

* jeśli dzwonisz Close, upewnij się, że robisz to w sposób bezpieczny dla wyjątków (np. w catch lub final block).


Kiedy mówisz „do ciebie należy przeprowadzenie niezbędnych badań”, co to za badanie? Jedyny sposób, w jaki wiem, jak to powiedzieć na pewno, to Refleksja, ale ma to tę wadę, że w większości sytuacji jest „nielegalna”.
Storm

7
Nie powiedziałbym: conn.Close(); // Optionalto nie jest opcjonalne. Jest to zbędne i niepotrzebne. Pozbywasz się obiektu dwa razy, co zostanie oznaczone jako ostrzeżenie przez niektóre narzędzia do analizy kodu.
Metalogic

@Metalogic Zgadzam się, że jest to zbędne i niepotrzebne (i brzydkie) wywoływanie Close z odpowiednim użyciem. Jednak dziurkowanie: wywołanie Close nie jest „usuwaniem” (podczas gdy Dispose implikuje Close dla SqlConnection). Porównaj z using (var x = ..) { x.Dispose(); }, w którym to przypadku xnaprawdę jest "usuwany dwukrotnie".
user2864740

11

Musisz wywołać Dispose ()!

Dispose () jest wywoływana przez programistę, a moduł wyrzucania elementów bezużytecznych wywołuje Finalize (). Jeśli nie wywołasz metody Dispose () na swoich obiektach, wszystkie używane przez nie niezarządzane zasoby nie zostaną usunięte, dopóki nie pojawi się moduł odśmiecania pamięci i nie wywoła ich finalizacji (i kto wie, kiedy to się stanie).

Ten scenariusz nazywa się niedeterministyczną finalizacją i jest częstą pułapką dla programistów .net. Jeśli pracujesz z obiektami, które implementują IDisposable, wywołaj na nich Dispose ()!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

Chociaż może istnieć wiele instancji (takich jak SqlConnection), w których wywołujesz Disponse () na jakimś obiekcie i po prostu wywołuje Close () na swoim połączeniu lub zamyka dojście do pliku, prawie zawsze najlepszym rozwiązaniem jest wywołanie Dispose ()! chyba że planujesz ponowne użycie obiektu w najbliższej przyszłości.


26
Ten komentarz jest całkowicie fałszywy. Śmieciarka nigdy, przenigdy nie dzwoni Dispose.
Stephen Cleary

3
Wniosek: Powinieneś wywołać, Dispose() jeśli nie używasz using()z klasą, która implementuje IDisposable. Jeśli nazywana klasa implementuje IDisposable i umieściłeś jej użycie na stronie w using()środku, możesz pozbyć się Dispose()(gra słów przeznaczona, więc zastrzel mnie). Close()Zalecane jest jednak używanie w przypadku wszystkiego, co jawnie wykorzystuje Open()AFAIK.
René Kåbis

Nie jestem pewien co do innych DBMS, ale NIE możesz zrobić obu w PostgreSql . Po Closenawiązaniu połączenia Postgres automatycznie ustawia identyfikator połączenia na null. Od tego Disposemomentu nie można już ustawić identyfikatora połączenia sql null.
ssd

10

Z SqlConnectionpunktu widzenia samego połączenia są one równoważne. Według Reflectora Dispose()wywołuje, Close()a także wykonuje kilka dodatkowych operacji zwalniających pamięć - głównie przez ustawienie elementów członkowskich równych null.

W przypadku Stream są one równoważne. Stream.Dispose()po prostu wywołuje Close ().


1
Jesteś pewny? MSDN twierdzi, że jest dziedziczony, zComponent którego nie wydaje się robić nic, aby spróbować zadzwonićClose() . Nie widzę nigdzie w DBConnectionlub SqlConnectionże więzi żadnej z tych zgłoszeń. Ma jednak prywatny, do DisposeMe()którego nie ma odniesienia nigdzie .
Deanna

@Deanna to jest nadpisane tutaj: github.com/dotnet/corefx/blob/ ...
David Cumps

@DavidCumps Wygląda na to, że zmieniło się to w ciągu 4 lat, odkąd napisałem ten komentarz. Moje linki są już nieaktualne.
Deanna


6

Ta niedoszła szybka rada stała się długą odpowiedzią. Przepraszam.

Jak zauważył Tyler w swojej miłej odpowiedzi, dzwonienie Dispose()jest świetną praktyką programistyczną. Dzieje się tak, ponieważ ta metoda ma „zebrać razem” wszystkie potrzebne zasoby, aby uwolnić zasoby, aby nie było niepotrzebnych otwartych zasobów. Jeśli na przykład napisałeś jakiś tekst do pliku i nie udało ci się zamknąć pliku (zwolnić zasób), pozostanie on otwarty i nikt inny nie będzie mógł do niego pisać, dopóki nie pojawi się GC i nie zrobi tego, co powinieneś był Gotowe.

Teraz, w niektórych przypadkach, będą "finalizujące" metody bardziej specyficzne dla klasy, z którą masz do czynienia, na przykład StreamWriter.Close(), która przesłania TextWriter.Close(). Rzeczywiście, są one zwykle bardziej dostosowane do sytuacji: Close()na przykład StreamWriter opróżnia strumień i bazowy koder przed Dispose()rozpoczęciem obiektu! Chłodny!

Jednak przeglądając MSDN zauważysz, że nawet Microsoft jest czasami zdezorientowany mnogością zamykaczy i urządzeń usuwających. Na przykład na tej stronie internetowej w niektórych przykładach Close()jest wywoływana przed niejawnym Dispose()(zobacz using instrukcję, jeśli nie rozumiesz, dlaczego jest niejawna), aw jednym szczególnie nie przeszkadzają. Dlaczego miałoby to być? Ja też byłem zakłopotany.

Doszedłem do wniosku (i podkreślam, że są to oryginalne badania i na pewno mogę stracić reputację, jeśli się mylę), że Close()może się nie powieść, dając wyjątek, pozostawiając otwarte zasoby, a jednocześnie Dispose()z pewnością je uwolni . Dlatego należy zawsze zabezpieczyć się rozmowy (przepraszam za kalambur).Dispose()Close()

MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

I tak, myślę, że Microsoft poślizgnął się na tym jednym przykładzie. Być może ta sygnatura czasowa nigdy nie zostanie pobrana do pliku.

Jutro poprawiam stary kod.

Edycja: przepraszam Brannon, nie mogę skomentować Twojej odpowiedzi, ale czy na pewno warto zadzwonić Close()na finallyblok? Myślę, że wyjątek od tego może zrujnować resztę bloku, który prawdopodobnie zawiera ważny kod czyszczący.

Odpowiedź Brannona: świetnie, po prostu nie zapomnij zadzwonić, Close()gdy jest to naprawdę potrzebne (np. W przypadku strumieni - nie wiem zbyt wiele o połączeniach SQL w .NET).


Właściwie nigdy nie wywołuję Close (), po prostu pozwalam Dispose () i konstrukcji „using” robić właściwą rzecz . Jeśli nie wywołujesz metody Dispose, musisz wywołać Close w sposób bezpieczny dla wyjątków. Dobrym pomysłem może być dodanie obsługi wyjątków do ostatniego bloku.
Brannon,

Racja, moje komentarze dotyczyły konkretnie SqlClient. Chodzi o to, że musisz zrozumieć klasy, z których korzystasz. Zawsze dzwonienie do Dispose niekoniecznie jest właściwą odpowiedzią.
Brannon,

2

Typecast do iDisposable i zadzwoń na ten temat. Spowoduje to wywołanie dowolnej metody skonfigurowanej jako implementująca „iDisposable.Dispose”, niezależnie od nazwy funkcji.


Funkcja „nosi nazwę” „Dispose”: więc wracamy do początkowego pytania:}
user2864740

Funkcja jest ograniczona IDisposable.Dispose, ale to nie znaczy, że taka jest nazwa. Zauważ, że w vb.net możliwe jest powiązanie funkcji z wieloma elementami członkowskimi interfejsu o nazwach, które nie muszą być powiązane z nazwą funkcji.
supercat

using (myObj as IDisposable)
Przesyłaj w

2

Generalnie mamy do czynienia z problemem w Close (), Abort () i Dispose (), ale pozwólcie, że powiem wam, jaka jest różnica między nimi.

1) PRZERWIJ: - Nie sugeruję używania tego, ponieważ gdy wywoływane jest przerwanie, klient usunie połączenie bez informowania serwera, więc serwer będzie czekał przez pewien czas (około 1 minuty). Jeśli masz żądanie zbiorcze, nie możesz użyć abort (), ponieważ może to spowodować przekroczenie limitu czasu dla ograniczonej puli połączeń.

2) Zamknij: - Zamknięcie jest bardzo dobrym sposobem na zamknięcie połączenia, ponieważ podczas zamykania połączenia zadzwoni do serwera i potwierdzi zamknięcie serwera również po tej stronie.

Tutaj jeszcze jedna rzecz do obejrzenia. W niektórych przypadkach, jeśli generuje się błąd, nie jest to dobry sposób, aby ostatecznie napisać kod w tym connection.close (), ponieważ w tym czasie wystąpi błąd w stanie komunikacji.

3) Utylizacja: - Jest to jeden rodzaj zamknięcia, ale po zamknięciu połączenia nie można go ponownie otworzyć.

Więc spróbuj w ten sposób

private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }

Sprawdzanie client != nulljest nieprawidłowe / wprowadzające w błąd, ponieważ nie chroni wszystkich zastosowań. Nie jestem też pewien, jak kod może dojść do stanu „to połączenie nie jest otwarte i powinno zostać zamknięte”.
user2864740
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.