GC.KeepAlive() when this is uncommented, still there is only one instance, how ?

大城市里の小女人 提交于 2019-12-08 12:13:27

问题


On against to the reference thread below, even if I comment the GC.KeepAlive() there is no difference I find and it blocks the creation of any other instance. Why is it the author explicitly mentioned its important line ?

Ensuring only one application instance


回答1:


If you don't do this the mutex will be destroyed on garbage collection but this isn't a guaranteed event to happen instantly that's why it can still work for a long time.

I would have used a static my self see the second answer.




回答2:


UPDATE: This question was the subject of my blog in June 2013; see that article for more thoughts on this subject. Thanks for the great question!


Let me just clarify what is going on here. Let's ignore the fact that it's a mutex and consider the general case:

class Foo 
{ 
    public Foo() 
    {
        this.x = whatever;
        this.y = whatever;
        SideEffects.Alpha(); // Does not use "this"
    }
    ~Foo() 
    { 
        SideEffects.Charlie(); 
    }
    ...
}
static class SideEffects
{
    public static void Alpha() {...}
    public static void Bravo() {...}
    public static void Charlie() {...}
    public static void M()
    {
        Foo foo = new Foo(); // Allocating Foo has side effect Alpha
        Bravo();
        // Foo's destructor has side effect Charlie
    }
}

The author of M desires that side effects Alpha(), Bravo() and Charlie() happen in that order. Now, you might reason that Alpha() must happen before Bravo() because Alpha() and Bravo() happen on the same thread, and C# guarantees that stuff that happens on the same thread preserves order of side effects. You would be correct.

You might reason that Charlie() must happen after Bravo() because Charlie() does not happen until the reference stored in foo is garbage collected, and local variable foo keeps that reference alive until control leaves the scope of foo, after the call to Bravo(). This is wrong. The C# compiler and the CLR jit compiler are permitted to work together to allow the local variable to be declared "dead" early. Remember, C# only guarantees that stuff happens in predictable order when observed from one thread. The garbage collector and finalizers run on their own threads! So it is legal for the garbage collector to deduce that foo -- which is not used in Bravo() -- is dead before the call to Bravo() and therefore side effect Charlie() can happen before Bravo() -- or during Bravo(), on another thread.

A KeepAlive(foo) would prevent the compilers from making this optimization; it tells the garbage collector that foo is alive at least until the KeepAlive and therefore the side effect Charlie() in the finalizer is guaranteed to come after side effect Bravo().

Now here's a fascinating question. Without the keepalive, can side effect Charlie() happen before Alpha()? It turns out, in some cases yes! The jitter is allowed to be very aggressive; if it discovers that the this of the ctor is going to be dead as soon as the ctor ends then it is allowed to schedule this for collection the moment after the ctor stops mutating fields of this.

Unless you do a KeepAlive(this) in the Foo() constructor, the garbage collector is permitted to collect this before Alpha() runs! (Provided nothing else is keeping it alive, of course.) An object may be finalized while its constructor is still running on another thread. This is yet another reason why you need to be incredibly careful writing classes that have destructors. The whole object needs to be designed to be robust in the face of the destructor suddenly being called unexpectedly on another thread because the jitter is aggressive.

In your specific case, "Alpha" is the side effect of taking out a mutex, "Bravo" is the side effect of running the entire program, and "Charlie" is the side effect of releasing the mutex. Without the keepalive, the mutex is permitted to be released before the program runs, or, more likely, while it is running. It is not required to be released, and most of the time it won't be released. But it could be, if the jitter decides to get aggressive about taking out the trash.


What are some alternatives?

The keepalive is correct, but personally I would not choose a keepalive in the original code, which was basically:

static void Main()
{
    Mutex m = GetMutex();
    Program.Run();
    GC.KeepAlive(m);
}

An alternative would be:

static Mutex m;
static void Main()
{
    m = GetMutex();
    Program.Run();
}

The jitter is permitted to kill local variables early but is not permitted to kill static variables early. Therefore no KeepAlive is necessary.

Even better would be to take advantage of the fact that Mutex is disposable:

static void Main()
{
    using(GetMutex())
       Program.Run();
}

This is a short way to write:

static void Main()
{
    Mutex m = GetMutex();
    try
    {
       Program.Run();
    }
    finally
    {
        ((IDisposable)m).Dispose();
    }
}

And now the garbage collector cannot clean up the mutex early because it needs to be disposed after control leaves Run. (If the jitter can prove that disposing the mutex does nothing then it is permitted to clean it up early, but in this case disposing the mutex has an effect.)




回答3:


No number of tests can prove that GC.KeepAlive is redundant, but just one (failed) test can prove that it is necessary.

In other words, if GC.KeepAlive is omitted then the code may not work correctly; it's not guaranteed to immediately break. And it may not work correctly because mutex is a local variable that goes out of scope; this makes it eligible for garbage collection. If and when the GC decides to collect that object, the mutex will be released (and you will be able to launch a new instance).



来源:https://stackoverflow.com/questions/15387964/gc-keepalive-when-this-is-uncommented-still-there-is-only-one-instance-how

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!