Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Finalization και Dispose

Μια από τις πιο χρήσιμες υπηρεσίες του CLR είναι το Garbage Collection. Δηλαδή ο περιοδικός καθαρισμός του Heap από objects που πλέον δεν χρησιμοποιούνται. Θαυμάσια, τώρα πια μπορούμε να δημιουργούμε όποιο αντικείμενο θέλουμε να το χρησιμοποιούμε για όσο θέλουμε και μετά.... να μή μας ενδιαφέρει ούτε να το "κλείσουμε" ούτε να το "καθαρίσουμε" ούτε τίποτα. Μετά από λίγη ώρα ο Garbage Collector θα καθαρίσει το Heap από τα αχρησιμοποίητα αντικείμενα.

 Αυτό ισχύει για τις περισσότερες των περιπτώσεων, όταν όμως ένας τύπος μας διαχειρίζεται unmanaged resources (για παράδειγμα database connections, queue ή file handlers), τότε πρέπει να "κλείνουμε" μόνοι μας αυτά τα resources. Στις ακόλουθες παραγράφους παραθέτω ένα πολύ γνωστό pattern για finalization και disposing τύπων που διαχειρίζονται unmanaged resources. Πρώτα όμως λίγη θεωρία.

Finalizers

Πρόκειται για μεθόδους που το όνομά τους ξεκινά με το "~" και έχουν το ίδιο όνομα με τον τύπο. Αν για παράδειγμα ο τύπος μας λέγεται MyClass ο finalizer του τύπου έχει το ακόλουθο signature:

 class MyClass
 {
        ~MyClass()
        {
//Your code here...
        }
 }

Ούτε void ούτε παράμετροι. Δεν είναι υποχρεωτικό να έχουμε finalizers για κάθε τύπο. Σε περίπτωση όμως που έχουμε τότε κατά τη διαδικασία του Garbage Collection του αντικειμένου μας γίνονται τα ακόλουθα δύο πράγματα:

  1. Το αντικείμενο "καθαρίζεται" από το heap (στην πραγματικότητα η μνήμη που καταλαμβάνει είναι και πάλι διαθέσιμη)
  2. Το αντικείμενο μπαίνει σε μια ουρά (queue) finalization. ΧΧΧμμμμμ τι είναι αυτή η finalization ουρά?, Πρόκειται για μιά κλασσική FIFO ουρά στην οποία κάθε αντικείμενο (πριν βγει από εκεί), υπόκειται σε finalization, δηλαδή απλά καλείται η finalize μεθοδός του.

Συνεπώς μπορείτε να φανταστείται το κώδικα του Garbage collector κάπως έτσι:

List<object> objectsToBeCGed = GetAllObjectsFromHeapToBeCGed();
List<object> objectsWithFinalizersToBeCGed = GetAllObjectsFromHeapToBeCGedWithFinalizers();

CleanHeapFromObjects(objectsToBeCGed );

FinalizationQueue.AddRange(objectsWithFinalizersToBeCGed.ToArray());

Προφανώς ο παραπάνω είναι ψευδω-κώδικας (και η λίστα objectsToBeCGed είναι υπερσύνολο του objectsWithFinalizersToBeCGed).

To CLR εποπτεύει την ουρά και κάθε φορά που βγάζει ένα αντικείμενο από αυτή καλεί τον Finalizer της.

Γιατί χρειαζόματε τους Finalizers?

Ας υποθέσουμε ότι φτιάχνουμε ένα τύπο (μιά κλάση) η οποία χειρίζεται unmanaged resources (π.χ. ένα database connection). Κάνουμε την κλάση να υλοποιεί το IDisposable Interface και στη Dispose μέθοδο έχουμε τον κώδικα που κλείνει το database connection:

    class MyClassThatHandlesDBConnection : IDisposable
    {
        ...
        public void Dispose()
        {
            ...
            this.DBConnection.Close();
        }
    }

Ολα φαίνονται εντάξει, αν κάποιος χρησιμοποιήσει την κλάσση με using ή καλέσει την Dispose πράγματι δεν θα έχουμε προβλήματα. Αν όμως ο χρήστης της κλάσης δεν τη χρησιμοποιήσει σωστά τότε θα έχουμε απρόσμενα αποτελέσματα και μάλλον δυσάρεστες εκπλήξεις οι οποίες ΔΕΝ φαίνονται στο UNIT TEST. Είναι ευθύνη του developer που υλοποιεί την MyClassThatHandlesDBConnection να τη φτιάξει όσο το δυνατό πιο bullet proof. Σε ένα τέτοιο σενάριο χρησιμοποίούμε τον Finalizer με το ακόλοθο pattern:

    class MyClassThatHandlesDBConnection : IDisposable
    {      
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~MyClassThatHandlesDBConnection()
        {
            //Clean up unmanaged resources only
            this.Dispose(false);
        }

        protected void Dispose(bool disposing)
        {
            if (disposing)
            {
                //Clean up all managed resources if disposing is true              
            }
            //Always clean up unmanaged resources regardless of disposing parameter
            this.DBConnection.Close();
        }      
    }

Από το παραπάνω snippet κώδικα παρατηρούμε τα ακόλουθα:

  • Αν χρησιμοποιήσουμε σωστά την κλάσση (με using ή καλώντας την Dispose() explicitly), τότε καθαρίζονται τα managed και unmanaged resources ΚΑΙ ΕΠΙΠΛΕΟΝ με τη δήλωση GC.SuppressFinalize(this); λέμε στον Garbage Collector να ΜΗ βάλει το αντικείμενο στην finalization ουρά όταν το κάνει collect από το heap, μιας και έχουμε φροντίσει να κάνουμε "finalize" το αντικείμενο εμείς (δηλαδή να καθαρίσουμε και τα unmanaged resources).
  • Στην περίπτωση που δεν χρησιμοποιηθεί η κλάσση σωστά (δηλαδή δεν γίνει dispose από τον developer που την χρησιμοποίησε), τότε και πάλι δεν θα έχουμε κακή συμπεριφορά -τουλάχιστο από την κλάσση μας- διότι έστω και αργά (αφού γίνει GCed και αφου βγεί από την Finalization ουρά) τα unmanaged resources θα "καθαριστούν" διότι θα κληθεί η ~MyClassThatHandlesDBConnection()

Αρα έχουμε φτιάξει μια κλάσση που ακόμα και αν δεν χρησιμοποιηθεί με το σωστό τρόπο, παρέχει τους μηχανισμούς για να μη μένουν unmanaged resources ανοιχτά. Προφανώς το καλύτερο σενάριο είναι να χρησιμοποιηθεί η κλάσση μας σωστά και να μην χρειαστεί να βασιστούμε στο finalization του CLR. Γιαυτό και πάντα πρέπει να χρησιμοποιούμε το using block ή να καλούμε το Dispose σε οποιοδήποτε Τύπο υλοποιεί το IDisposable.

Μπορούμε λοιπόν να διατυπώσουμε τον κανόνα ότι όταν φτιάχνουμε κλάσσεις που χειρίζονται unmanaged resources, καλό είναι να κάνουμε finalization και disposing με το παραπάνω pattern.

Ελπίζω να σας φανεί χρήσιμο.....

Με την ευκαιρία εύχομαι ΚΑΛΗ ΧΡΟΝΙΑ ΜΕ ΥΓΕΙΑ ΚΑΙ ΕΥΤΥΧΙΑ ΣΕ ΟΛΟΥΣ.

Έχουν δημοσιευτεί Κυριακή, 30 Δεκεμβρίου 2007 1:26 μμ από το μέλος Nick Makris
Δημοσίευση στην κατηγορία:

Ενημέρωση για Σχόλια

Αν θα θέλατε να λαμβάνετε ένα e-mail όταν γίνονται ανανεώσεις στο περιεχόμενο αυτής της δημοσίευσης, παρακαλούμε γίνετε συνδρομητής εδώ

Παραμείνετε ενήμεροι στα τελευταία σχόλια με την χρήση του αγαπημένου σας RSS Aggregator και συνδρομή στη Τροφοδοσία RSS με σχόλια

Σχόλια:

Χωρίς Σχόλια

Ποιά είναι η άποψή σας για την παραπάνω δημοσίευση;

(απαιτούμενο)
απαιτούμενο
(απαιτούμενο)
ÅéóÜãåôå ôïí êùäéêü:
CAPTCHA Image