0

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

Community
  • 1
  • 1
Jasmine
  • 5,186
  • 16
  • 62
  • 114
  • I guess because in your application it is not being garbage collected. It is there to stop the mutex being garbage collected. You need to run your program such that the garbage collector is called then see if this effects your application. – TheKingDave Mar 13 '13 at 14:27
  • @TheKingDave: oh ok, thank you. But VS confuses me if I just use those code and want to make a learning conclusion as a newbie. – Jasmine Mar 13 '13 at 14:30

3 Answers3

12

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.)

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Hi Eric, thank you so much for the very detailed explanation with a very good example on all possible scenarios :) Its a lot of thought provoking possibilities in programming and you explained it elegantly. I am afraid to ask you about the "using" in this scenario as because using will not destroy the object only within the scope of it but will dispose it when the object goes outside it. Sorry, also I am trying to understand why you said you will choose static field rather than keepalive. – Jasmine Mar 13 '13 at 15:40
  • I am sorry, I am afraid asking you silly questions, however I am very less experienced and trying to understand concepts slowly day by day. :( – Jasmine Mar 13 '13 at 15:44
  • Hi Eric, thank you so much again for the very detailed explanation, it helps me a lot and now I am confident on this :) Once again I would like to thank you for your precious time in helping me :) Cheers – Jasmine Mar 13 '13 at 16:05
2

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.

Dreamwalker
  • 3,032
  • 4
  • 30
  • 60
  • Thank you, what you mean by "I would have used a static my self see the second answer."? – Jasmine Mar 13 '13 at 14:28
  • The linked question has an answer below the accepted which uses a static variable instead – Dreamwalker Mar 13 '13 at 14:30
  • Oh ok yes. So the following will work ? [STAThread] static void Main() { bool result; public static System.Threading.Mutex mutex = new System.Threading.Mutex(true, "UniqueAppId", out result); if (!result) { MessageBox.Show("Another instance is already running."); return; } Application.Run(new Form1()); GC.KeepAlive(mutex); // mutex shouldn't be released - important line } – Jasmine Mar 13 '13 at 14:33
  • another thing to note in the second example he is giving the previous instance time to shutdown before refusing to load the new instance – Dreamwalker Mar 13 '13 at 14:36
  • Uhmm I really dont understand it. Well it gives me error to define like this public static System.Threading.Mutex mutex; mutex= new System.Threading.Mutex(true, "UniqueAppId", out result); – Jasmine Mar 13 '13 at 14:37
  • ok I think he is declaring inside class but I tried it inside a method, ahh bad... Thank you :) However I will understand it still better in VS now :) I am newbie.... :( – Jasmine Mar 13 '13 at 14:40
  • @Dreamwalker FYI you shouldn't say "the answer below this" as people can have a variety of different sorts set, and if there are more then 2 answers you are most likely pointing to the wrong one. – asawyer Mar 13 '13 at 16:00
  • @asawyer good point I was actually looking for a way to link the answer on the question – Dreamwalker Mar 13 '13 at 16:08
  • @Dreamwalker at the bottom left of the question is a Share link, it will give a URL to the question – asawyer Mar 13 '13 at 17:02
1

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).

Jon
  • 428,835
  • 81
  • 738
  • 806