12. Disposal and Garbage Collection || C#10 Flashcards
What is a disposal and garbage disposal in c#?
Disposal - Its a specific code required by some objects to release resources such as open files, locks, operating system handles, and unmanaged objects. It is supported by IDisposable
Garbage collection - the release of managed memory that was occupied by unused objects. It is performed by the CLR.
Are disposal and garbage collection the same thing in c#?
Disposal differs from garbage collection in that disposal is usually explicitly instigated; garbage collection is tottaly automatic. In other words, the programmer takes care of such things as releasing file handles, locks and operating system resources, while the CLR takes care of releasing memory.
What is a good example of disposal code?
Good example is a try/finally block, where the finally block ensure that the Dispose
method is called even when an exception is thrown or the code exits the block early.
Another example is the using...
statement like:using FileStream fs = new FileStream("myFile.txt", FileMode.Open);
The following syntax ensures exposal as soon as fs goes out of scope (does not use fs
anymore)
Name de facto rules of disposal logic (its not hardwired, just a logic to define a cosistent protocol)
- After an object has been disposed, it’s beyond redeption. It cannot be reactivated, and calling its methods or properties (other than Dispose) throws an
ObjectDisposedException
; - Calling an object’s
Dispose
method repeatedly causes no error; - If disposable object x “owns” disposable object y, x’s Dispose method automatically calls y’s Dispose method - unless instructed otherwise;
Some objects may have both Dispose and Close methods. Is there a difference between them?
The .NET BCL is not completely consistent on the semantics of a Close method, although in nearly all cases it’s either of the following:
1. Functionally identical to Dispose
2. A functional subset of Dispose
An example might be a DbConnection: a closed connection may be re-opened but a disposed one can never be re-disposed
There is this safe rule - “if in doubt, dispose” - but are there scenarios when we shouldnt dispose the object?
There are 3 scenarios for not disposing:
1. When you don’t “own” the object - for example, when obtaining a shared object via a static field or property;
2. When an object’s Dispose
method does something that you don’t want;
3. When an object’s Dispose
method is unnecessary by design and disposing that object would add complexity to your program;
What is an anonymous disposal pattern?
Let’s say we want to implement IDisposable
without having to write a class for it. Instead we can just write this:
~~~
public class Disposable: IDisposable
{
public static Disposable Create (Action onDispose) => new Disposable (onDispose);
Action _OnDispose
Disposable (Action onDispose) => _onDispose = onDispose;
public void Dispose()
{
_onDispose?.Invoke(); // Execute if not null
_onDispose = null; // Ensure it can’t execute a second time
}
}
~~~
In here the class was created, but it serves as a factory for creating instances of disposable objects. By using this pattern, you can create disposable objects without explicitly defining a separate class for each disposable instance. Instead, you can use the Create method to create instances and specify the disposal code using an Action delegate, making the implementation more concise and flexible.
How does the Garbage Collection (GC) work, in general?
Regardless of whether an object requires a Dispose method for custom tear-down logic, at some point the memory it occupies on the heap must be freed. The CLR handles this side of it entirely automatically via an automatic Garbage Collection (GC). You never deallocate managed memory yourself.
If we have a method that contains a local variable, after this method is used and exits, the local variable pops out of scope, meaning that nothing is left to reference the array on the memory heap. The orphaned array then becomes eligible to be reclaimed in GC.
In debug mode with optimization disabled, the lifetime of an object referenced by a local variable extends to the end of the code block to ease debugging. Otherwise, it becomes eligible for collection at the earliest point at which it’s no longer used.
Garbage collection does not happen immediately after an object is orphaned - it happens periodically, but not to a fixed schedule. The CLR decides when to collect upon a number of factors: available memory, the amount of memory allocation and the time since the last collection. The timespac between when an object was orphaned until when it is collected can span from nanoseconds to days.
What is a ‘root’?
A root is something that keeps an object alive. If an object is not directly or indirectly referenced by a root it will be eligible for garbage collection.
A root is one of the following:
* A local variable or parameter in an executing method (or in any method in its call stack);
* A static variable;
* An object on the queue that stores objects ready for finalization
To put it in another way, objects that cannot be accessed by following the references from a root object are unreachable - and therefore subject to collection
What is a finalizer and how it is related to garbage collection?
Prior to an object being released from memory, its finalizer runs, if it has one. A finalizer is declared like. constructor but it is prefixed with ~ symbol:
~~~
class Test
{
~Test()
{
// Finalizer logic
}
}
~~~
Although similar in declaration to a constructor, finalizers cannot be declared as public or static, cannot have parameters and cannot call the base class.
Finalizers are possible because garbage collection works in distinct phases. First, the GC identifies the unused objects ripe for deletion. Those without finalizers are deleted immediately. Those with pending (unrun) finalizers are kept alive (for now) and are put onto a special queue.
At that point garbage collection is complete and your program continues executing. The finalizer thread then kicks in and starts running in parallel to your program, picking objects off that special queue and running their finalization methods. Prior to each objects finalizer running, it’s still very mutch alive - that queue acts as a root object. After it’s been dequeued and the finalizer executed the object becomes orphaned and will be deleted in the next collection.
What are the conditions/restrictions that the finalizers bring?
- Finalizers slow the allocation and collection of memory (the GC needs to keep track of which finalizers have run);
- Finalizers prolong the life of the object and any reffered objects (they must wait for the next garbage collection for actual deletion).
- It’s imposible to predict in what order the finalizers for a set of objects will be called.
- You have limited control over when the finalizer for an object will be called.
- If code in a finalizer blocks,other objects cannot be finalized.
- Finaliezers can be circumvented altogether if an application fails to unload cleanly;
In summary, finalizers are somewhat like lawyers - although there are cases in which you really need them, in general you don’t want to use them unless absolutely necessary. If you do use them, you need to be 100% sure you understand what they are doing for you.
What are general guidelines for implementing finalizers?
- Ensure that your finalizer executes quickly;
- Never block in your finalizer;
- Don’t reference other finalizable objects;
- Don’t throw exceptions
Describe a pattern where Dispose method is called from the Finalizer
This makes sense when cleanup is not urgent and calling Dispose is more of an optimization than a necessity. In this pattern we couple memort deallocation and resource deallocation - two things with potentially divergent interests (unless the resource is memory itself). This pattern also serves as a backup when a consumer simply forgets to call Dispose()
Describe pattern implementation where Dispose method is called from the Finalizer
Describe both scenarios where the disposing flag is true/false
Dispose()
is overloaded to accept a bool disposing flag. The parametless version is not declared as virtual and simply calls the enhanced version with true.
The enhanced version contains the actual disposal logic and is protected and virtual; this provides a safe point for subclasses to add their own disposal logic. The disposing flag means its being called “properly” from the Dispose method rather than in “last-resort mode” from the finalizer. The idea is that when called with disposing set to “false”, this method should not, in general, reference other objects with finalizers (because such objects might themselves have been finalized and so be in an unpredictable state).
Here are a couple of tasks that the Dispose method can still perform in last-resort mode, when disposing flag is false:
* Releasing any direct references to OS resource (obtained, perhaps, via a P/Invoke call to the Win32 API);
* Deleting a temporary file created on construction;
Notice that we call GC.SuppressFinalize in the parametless Dispose method - this prevents the finalizer from running when GC later catches up with it. Technically this is unnecessary given than Dispose methods must tolerate repeated calls. However, doing so improves performance because it allows the object (and its referenced objects) to be garbage collected in a single cycle.
What is a ‘resurrection’ in Disposal and Garbage Collection?
Suppose a finalizer modifies a living object that refers back to the dying object. When the next garbage collection happens (for the object’s generation) the CLR will see the previously dying object as no longer orphaned - and so it will evade garbage collection. This is an advanced scenario and is called resurrection