简体   繁体   中英

What happens if I don't call Dispose on the pen object?

What happens if I don't call Dispose on the pen object in this code snippet?

private void panel_Paint(object sender, PaintEventArgs e)
{
    var pen = Pen(Color.White, 1);
    //Do some drawing
}

A couple of corrections should be made here:

Regarding the answer from Phil Devaney:

"...Calling Dispose allows you to do deterministic cleanup and is highly recommended."

Actually, calling Dispose() does not deterministically cause a GC collection in .NET - ie it does NOT trigger a GC immediately just because you called Dispose(). It only indirectly signals to the GC that the object can be cleaned up during the next GC (for the Generation that the object lives in). In other words, if the object lives in Gen 1 then it wouldn't be disposed of until a Gen 1 collection takes place. One of the only ways (though not the only) that you can programmatically and deterministically cause the GC to perform a collection is by calling GC.Collect(). However, doing so is not recommended since the GC "tunes" itself during runtime by collecting metrics about your memory allocations during runtime for your app. Calling GC.Collect() dumps those metrics and causes the GC to start its "tuning" all over again.

Regarding the answer:

IDisposable is for disposing unmanaged resources. This is the pattern in .NET.

This is incomplete. As the GC is non-deterministic, the Dispose Pattern, ( How to properly implement the Dispose pattern ), is available so that you can release the resources you are using - managed or unmanaged. It has nothing to do with what kind of resources you are releasing. The need for implementing a Finalizer does have to do with what kind of resources you are using - ie ONLY implement one if you have non-finalizable (ie native) resources. Maybe you are confusing the two. BTW, you should avoid implementing a Finalizer by using the SafeHandle class instead which wraps native resources which are marshaled via P/Invoke or COM Interop. If you do end up implementing a Finalizer, you should always implement the Dispose Pattern.

One critical note which I haven't seen anyone mention yet is that if disposable object is created and it has a Finalizer (and you never really know whether they do - and you certainly shouldn't make any assumptions about that), then it will get sent directly to the Finalization Queue and live for at least 1 extra GC collection .

If GC.SuppressFinalize() is not ultimately called, then the finalizer for the object will be called on the next GC. Note that a proper implementation of the Dispose pattern should call GC.SuppressFinalize(). Thus, if you call Dispose() on the object, and it has implemented the pattern properly, you will avoid execution of the Finalizer. If you don't call Dispose() on an object which has a finalizer, the object will have its Finalizer executed by the GC on the next collection. Why is this bad? The Finalizer thread in the CLR up to and including .NET 4.6 is single-threaded. Imagine what happens if you increase the burden on this thread - your app performance goes to you know where.

Calling Dispose on an object provides for the following:

  1. reduce strain on the GC for the process;
  2. reduce the app's memory pressure;
  3. reduce the chance of an OutOfMemoryException (OOM) if the LOH (Large Object Heap) gets fragmented and the object is on the LOH;
  4. Keep the object out of the Finalizable and f-reachable Queues if it has a Finalizer;
  5. Make sure your resources (managed and unmanaged) are cleaned up.

Edit : I just noticed that the "all knowing and always correct" MSDN documentation on IDisposable (extreme sarcasm here) actually does say

The primary use of this interface is to release unmanaged resources

As anyone should know, MSDN is far from correct, never mentions or shows 'best practices', sometimes provides examples that don't compile, etc. It is unfortunate that this is documented in those words. However, I know what they were trying to say: in a perfect world the GC will cleanup all managed resources for you (how idealistic); it will not, however cleanup unmanaged resources. This is absolutely true. That being said, life is not perfect and neither is any application. The GC will only cleanup resources that have no rooted-references. This is mostly where the problem lies.

Among about 15-20 different ways that .NET can "leak" (or not free) memory, the one that would most likely bite you if you don't call Dispose() is the failure to unregister/unhook/unwire/detach event handlers/delegates. If you create an object that has delegates wired to it and you don't call Dispose() (and don't detach the delegates yourself) on it, the GC will still see the object as having rooted references - ie the delegates. Thus, the GC will never collect it.

@joren's comment/question below (my reply is too long for a comment):

I have a blog post about the Dispose pattern I recommend to use - ( How to properly implement the Dispose pattern ). There are times when you should null out references and it never hurts to do so. Actually, doing so does do something before GC runs - it removes the rooted reference to that object. The GC later scans its collection of rooted references and collects those that do not have a rooted reference. Think of this example when it is good to do so: you have an instance of type "ClassA" - let's call it 'X'. X contains an object of type "ClassB" - let's call this 'Y'. Y implements IDisposable, thus, X should do the same to dispose of Y. Let's assume that X is in Generation 2 or the LOH and Y is in Generation 0 or 1. When Dispose() is called on X and that implementation nulls out the reference to Y, the rooted reference to Y is immediately removed. If a GC happens for Gen 0 or Gen 1, the memory/resources for Y is cleaned up but the memory/resources for X is not since X lives in Gen 2 or the LOH.

The Pen will be collected by the GC at some indeterminate point in the future, whether or not you call Dispose .

However, any unmanaged resources held by the pen (eg, a GDI+ handle) will not be cleaned up by the GC. The GC only cleans up managed resources. Calling Pen.Dispose allows you to ensure that these unmanaged resources are cleaned up in a timely manner and that you aren't leaking resources.

Now, if the Pen has a finalizer and that finalizer cleans up the unmanaged resources then those said unmanaged resources will be cleaned up when the Pen is garbage collected. But the point is that:

  1. You should call Dispose explicitly so that you release your unmanaged resources, and
  2. You shouldn't need to worry about the implementation detail of if there is a finalizer and it cleans up the unmanaged resources.

Pen implements IDisposable . IDisposable is for disposing unmanaged resources. This is the pattern in .NET.

For previous comments on the this topic, please see this answer .

The underlying GDI+ pen handle will not be released until some indeterminate time in the future ie when the Pen object is garbage collected and the object's finalizer is called. This might not be until the process terminates, or it might be earlier, but the point is its non-deterministic. Calling Dispose allows you to do deterministic cleanup and is highly recommended.

If you really want to know how bad it is when you don't call Dispose on graphics objects you can use the CLR Profiler, available free for the download here. In the installation folder (defaults to C:\\CLRProfiler ) is CLRProfiler.doc which has a nice example of what happens when you don't call Dispose on a Brush object. It is very enlightening. The short version is that graphics objects take up a larger chunk of memory than you might expect and they can hang around for a long time unless you call Dispose on them. Once the objects are no longer in use the system will, eventually, clean them up, but that process takes up more CPU time than if you had just called Dispose when you were finished with the objects. You may also want to read up on using IDisposable here and here .

The total amount of .Net memory in use is the .Net part + all 'external' data in use. OS objects, open files, database and network connections all take some resources that are not purely .Net objects.

Graphics uses Pens and other objects wich are actually OS objects that are 'quite' expensive to keep around. (You can swap your Pen for a 1000x1000 bitmap file). These OS objects only get removed from the OS memory once you call a specific cleanup function. The Pen and Bitmap Dispose functions do this for you immediatly when you call them.

If you don't call Dispose the garbage collector will come to clean them up 'somewhere in the future*'. (It will actually call the destructor/finalize code that probably calls Dispose())

*on a machine with infinite memory (or more then 1GB) somewhere in the future can be very far into the future. On a machine doing nothing it can be easily longer then 30 minutes to clean up that huge bitmap or very small pen.

它将保留资源,直到垃圾收集器清理它

Depends if it implements finalizer and it calls the Dispose on its finalize method. If so, handle will be released at GC.

if not, handle will stay around until process is terminated.

With graphic stuff it can be very bad.

Open the Windows Task Manager. Click "choose columns" and choose column called "GDI Objects".

If you don't dispose certain graphic objects, this number will keep raising and raising.

In older versions of Windows this can crash the whole application (limit was 10000 as far as I remember), not sure about Vista/7 though but it's still a bad thing.

垃圾收集器无论如何都会收集它但是它很重要:如果你不打电话处理你不使用它的物体,它将在记忆中存活更长时间并被提升到更高代,这意味着收集它的成本更高。

在我的脑海里,第一个想法来到表面的是,一旦方法完成执行,这个对象将被处置!我不知道我在哪里得到这个信息!是不是?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM