Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
140 views
in Technique[技术] by (71.8m points)

c# - Which objects can I use in a finalizer method?

I have a class that should delete some file when disposed or finalized. Inside finalizers I can't use other objects because they could have been garbage-collected already.

Am I missing some point regarding finalizers and strings could be used?

UPD: Something like that:

public class TempFileStream : FileStream
{
    private string _filename;

    public TempFileStream(string filename)
        :base(filename, FileMode.Open, FileAccess.Read, FileShare.Read)
    {
        _filename = filename;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (_filename == null) return;

        try
        {
            File.Delete(_filename); // <-- oops! _filename could be gc-ed already
            _filename = null;
        }
        catch (Exception e)
        {
            ...
        }
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Yes, you can most certainly use strings from within a finalizer, and many other object types.

For the definitive source of all this, I would go pick up the book CLR via C#, 3rd edition, written by Jeffrey Richter. In chapter 21 this is all described in detail.

Anyway, here's what is really happening...

During garbage collection, any objects that have a finalizer that still wants to be called are placed on a special list, called the freachable list.

This list is considered a root, just as static variables and live local variables are. Therefore, any objects those objects refer to, and so on recursively is removed from the garbage collection cycle this time. They will survive the current garbage collection cycle as though they weren't eligible to collect to begin with.

Note that this includes strings, which was your question, but it also involves all other object types

Then, at some later point in time, the finalizer thread picks up the object from that list, and runs the finalizer on those objects, and then takes those objects off that list.

Then, the next time garbage collection runs, it finds the same objects once more, but this time the finalizer no longer wants to run, it has already been executed, and so the objects are collected as normal.

Let me illustrate with an example before I tell you what doesn't work.

Let's say you have objects A through Z, and each object references the next one, so you have object A referencing object B, B references C, C references D, and so on until Z.

Some of these objects implement finalizers, and they all implement IDisposable. Let's assume that A does not implement a finalizer but B does, and then some of the rest does as well, it's not important for this example which does beyond A and B.

Your program holds onto a reference to A, and only A.

In an ordinary, and correct, usage pattern you would dispose of A, which would dispose of B, which would dispose of C, etc. but you have a bug, so this doesn't happen. At some point, all of these objects are eligible for collection.

At this point GC will find all of these objects, but then notice that B has a finalizer, and it has not yet run. GC will therefore put B on the freachable list, and recursively take C, D, E, etc. up to Z, off of the GC list, because since B suddenly became in- eligible for collection, so does the rest. Note that some of these objects are also placed on the freachable list themselves, because they have finalizers on their own, but all the objects they refer to will survive GC.

A, however, is collected.

Let me make the above paragraph clear. At this point, A has been collected, but B, C, D, etc. up to Z are still alive as though nothing has happened. Though your code no longer has a reference to any of them, the freachable list has.

Then, the finalizer thread runs, and finalizes all of the objects in the freachable list, and takes the objects off of the list.

The next time GC is run, those objects are now collected.

So that certainly works, so what is the big bruaha about?

The problem is with the finalizer thread. This thread makes no assumptions about the order in which it should finalize those objects. It doesn't do this because in many cases it would be impossible for it to do so.

As I said above, in an ordinary world you would call dispose on A, which disposes B, which disposes C, etc. If one of these objects is a stream, the object referencing the stream might, in its call to Dispose, say "I'll just go ahead and flush my buffers before disposing the stream." This is perfectly legal and lots of existing code do this.

However, in the finalization thread, this order is no longer used, and thus if the stream was placed on the list before the objects that referenced it, the stream is finalized, and thus closed, before the object referencing it.

In other words, what you cannot do is summarized as follows:

You can not access any objects your object refer to, that has finalizers, as you have no guarantee that these objects will be in a usable state when your finalizer runs. The objects will still be there, in memory, and not collected, but they may be closed, terminated, finalized, etc. already.

So, back to your question:

Q. Can I use strings in finalizer method?
A. Yes, because strings do not implement a finalizer, and does not rely on other objects that has a finalizer, and will thus be alive and kicking at the time your finalizer runs.

The assumption that made you take the wrong path is the second sentence of the qustion:

Inside finalizers I can't use other objects because they could have been garbage-collected already.

The correct sentence would be:

Inside finalizer I can't use other objects that have finalizers, because they could have been finalized already.


For an example of something the finalizer would have no way of knowing the order in which to correctly finalize two objects, consider two objects that refer to each other and that both have finalizers. The finalizer thread would have to analyze the code to determine in which order they would normally be disposed, which might be a "dance" between the two objects. The finalizer thread does not do this, it just finalizes one before the other, and you have no guarantee which is first.


So, is there any time it is safe to access objects that also have a finalizer, from my own finalizer?

The only guaranteed safe scenario is when your program/class library/source code owns both objects so that you know that it is.

Before I explain this, this is not really good programming practices, so you probably shouldn't do it.

Example:

You have an object, Cache, that writes data to a file, this file is never kept open, and is thus only open when the object needs to write data to it.

You have another object, CacheManager, that uses the first one, and calls into the first object to give it data to write to the file.

CacheManager has a finalizer. The semantics here is that if the manager class is collected, but not disposed, it should delete the caches as it cannot guarantee their state.

However, the filename of the cache object is retrievable from a property of the cache object.

So the question is, do I need to make a copy of that filename into the manager object, to avoid problems during finalization?

Nope, you don't. When the manager is finalized, the cache object is still in memory, as is the filename string it refers to. What you cannot guarantee, however, is that any finalizer on the cache object hasn't already run.

However, in this case, if you know that the finalizer of the cache object either doesn't exist, or doesn't touch the file, your manager can read the filename property of the cache object, and delete the file.

However, since you now have a pretty strange dependency going on here, I would certainly advice against it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...