A mutex puzzle; single instance of application

  • Thread starter Michael A. Covington
  • Start date
M

Michael A. Covington

See:
http://www.ai.uga.edu/mc/SingleInstance.html

While attempting to use a mutex to allow only one instance of my app to run
at a time (Recipe 4.12 in C# Programmer's Cookbook), I found that if the
mutex is in a local variable in Main(), and my program launches any windows
before Application.Run(), it will lose the mutex upon doing so. It
shouldn't, as far as I can see. Moving the mutex variable outside Main()
and making it static eliminates the problem.

Any ideas? Is there a subtle matter of variable scope and extent that I
haven't understood? Does a process lose its mutexes when it launches a
window? Why?

(The mutex variable still exists, and m.Handle has the same value; but
according to ProcessExplorer, the mutex no longer exists. And all of this
happens only for processes launched from Windows, not under the IDE!)

--

Michael A. Covington - Artificial Intelligence Ctr - University of Georgia

"In the core C# language it is simply not possible to have an uninitialized
variable, a 'dangling' pointer, or an expression that indexes an array
beyond its bounds. Whole categories of bugs that routinely plague C and C++
programs are thus eliminated." - A. Hejlsberg, The C# Programming Language
 
J

Jon Skeet [C# MVP]

Michael A. Covington said:
See:
http://www.ai.uga.edu/mc/SingleInstance.html

While attempting to use a mutex to allow only one instance of my app to run
at a time (Recipe 4.12 in C# Programmer's Cookbook), I found that if the
mutex is in a local variable in Main(), and my program launches any windows
before Application.Run(), it will lose the mutex upon doing so. It
shouldn't, as far as I can see. Moving the mutex variable outside Main()
and making it static eliminates the problem.

Any ideas? Is there a subtle matter of variable scope and extent that I
haven't understood? Does a process lose its mutexes when it launches a
window? Why?

(The mutex variable still exists, and m.Handle has the same value; but
according to ProcessExplorer, the mutex no longer exists. And all of this
happens only for processes launched from Windows, not under the IDE!)

My guess is that the JIT can notice that the Mutex is no longer
referenced, and thus marks it as available for garbage collection. As a
way of testing this, put the line:

GC.KeepAlive (myMutex);

at the *end* of the Main method.

Let me know if that works, and I'll mention it in the FAQ.
 
M

Michael A. Covington

Jon Skeet said:
My guess is that the JIT can notice that the Mutex is no longer
referenced, and thus marks it as available for garbage collection. As a
way of testing this, put the line:

GC.KeepAlive (myMutex);

at the *end* of the Main method.

Let me know if that works, and I'll mention it in the FAQ.

I'll try that. Note that inserting additional references to the mutex
variable
(e.g., MessageBox(m.Handle.ToString()) later on in the Main() method did not
help. If the GC thinks I'm no longer using the mutex variable, it's
mistaken.

In any case I'll investigate. Thanks!
 
M

Michael A. Covington

So far, the problem is remarkably hard to reproduce, and I don't want to
post the program in which it actually is observed because it's proprietary
(for a client).

But here's a question.

Should there be any difference between this:

Application.Run(new Form1());

and this?

Form1 f1 = new Form1();
f1.Show();
Application.Run();

Would the latter keep the garbage collector from realizing that f1's
variables need to persist even when f1 is not visible?
 
M

Michael A. Covington

Dear Jon and others,

Adding GC.KeepAlive(mutex) at the end of the code worked just as well as
making the mutex variable static.

Below is my Main() method, with some names changed to conceal what piece of
software this is. I should add that LaunchFormEdit is not involved in the
cases I'm testing with, and that FormOpeningMenu has a Closing event handler
that calls Application.Exit.

Further wisdom would be welcome!
Michael

[STAThread]
static void Main(string[] args)
{

// 1. Determine that this is the only instance running

bool createdMutex;
System.Threading.Mutex mutex = new
System.Threading.Mutex(true,"asdfasdf",out createdMutex);
// See the GC.KeepAlive below. I don't know why it's necessary.

if (!createdMutex)
{
string msg = "asdfasdfasdf is already running.\r\n\r\n";
System.Windows.Forms.MessageBox.Show(msg,"asdfasdfasdfasdf");
return;
}


// 2. Make a Help Window.
FormHelp formHelp = new FormHelp();
formHelp.Show(); // Without the GC.KeepAlive, the mutex is released
HERE against my wishes.

// 3. Make an Opening Menu.
theOpeningMenu = new FormOpeningMenu();


// 4. Start in either the Opening Menu or FormEdit depending on args.
if (args.Length == 0)
theOpeningMenu.Show();
else
LaunchFormEdit(args[0]);

// 5. Let 'er rip! In either of 2 windows as appropriate.
// They both have Closing event handlers to do Application.Exit() when
the window closes.
Application.Run();
GC.KeepAlive(mutex);
}
 
M

Michael A. Covington

Jon,

You got it right. It's a matter of the optimizing compiler outsmarting me.

The optimizing compiler is realizing that there are no operations on the
mutex after a certain point (that can't be moved earlier without affecting
the program), so it's disposing of the mutex much earlier than it ought to.

GC.KeepAlive() fixes it, and this is more elegant (and presumably more
reliable) than my solution involving a static global variable.

Thanks! I'm about to revise the full story, which is on
www.ai.uga.edu/mc/SingleInstance.html.


Michael Covington
Associate Director
Artificial Intelligence Center
The University of Georgia
 
W

William Stacey [MVP]

Not sure it has to do with mutex per se. It is more how your hooking up (or
not) to the message loop AFAICT. The following works as expected from the
perspective that the form is displayed and replies to messages and when you
close the form, the Console writes happen. If you remove the "form2" from
the Application.Run() line, the lines after it, do not run. Test it to see
what I mean. To be safe, I would use the standard means of running
Application.Run(new form()) as not sure of side effects of not doing it.

[STAThread]
static void Main()
{
int myVar = 10;
string myString = "Hello";
Form2 form2 = new Form2();
form2.Show();
Application.Run(form2);
Console.WriteLine("myVar:"+myVar);
Console.WriteLine("myString:"+myString);
Console.ReadLine();
}
 
M

Michael A. Covington

William Stacey said:
Not sure it has to do with mutex per se. It is more how your hooking up (or
not) to the message loop AFAICT. The following works as expected from the
perspective that the form is displayed and replies to messages and when you
close the form, the Console writes happen. If you remove the "form2" from
the Application.Run() line, the lines after it, do not run. Test it to see
what I mean. To be safe, I would use the standard means of running
Application.Run(new form()) as not sure of side effects of not doing it.

[STAThread]
static void Main()
{
int myVar = 10;
string myString = "Hello";
Form2 form2 = new Form2();
form2.Show();
Application.Run(form2);
Console.WriteLine("myVar:"+myVar);
Console.WriteLine("myString:"+myString);
Console.ReadLine();
}

It turns out that my use of Application.Run() was not the problem.
Application.Run() leaves it up to you to create and show all your windows,
and also make sure that the appropriate one (or more) has a Closing event
handler that ends the application (Application.Exit()). This I did. See my
other message for the answer to the mutex problem...
 
W

William Stacey [MVP]

The keep alive may get around the issue and glad it works for. However, try
what I showed to reproduce the same thing without the mutex and you don't
need the keep alive. Not hooking up the form inside the Run() does not seem
to be a good thing. And it may have other side effects we don't see at the
moment.

--
William Stacey, MVP

Michael A. Covington said:
William Stacey said:
Not sure it has to do with mutex per se. It is more how your hooking up (or
not) to the message loop AFAICT. The following works as expected from the
perspective that the form is displayed and replies to messages and when you
close the form, the Console writes happen. If you remove the "form2" from
the Application.Run() line, the lines after it, do not run. Test it to see
what I mean. To be safe, I would use the standard means of running
Application.Run(new form()) as not sure of side effects of not doing it.

[STAThread]
static void Main()
{
int myVar = 10;
string myString = "Hello";
Form2 form2 = new Form2();
form2.Show();
Application.Run(form2);
Console.WriteLine("myVar:"+myVar);
Console.WriteLine("myString:"+myString);
Console.ReadLine();
}

It turns out that my use of Application.Run() was not the problem.
Application.Run() leaves it up to you to create and show all your windows,
and also make sure that the appropriate one (or more) has a Closing event
handler that ends the application (Application.Exit()). This I did. See my
other message for the answer to the mutex problem...
 
W

Willy Denoyette [MVP]

If you mean the "JIT compiler", I agree.
An object reference becomes a candidate for collection as soon as it is no
longer used. A variable that goes out of scope or whose contents can not be
accessed (execution has passed the last use of the variable) is no longer
counted as a live reference by the JITter. This is different when
debugging, the life times of variables will be extended, so that they can be
inspected even after they are (officially) no longer used.

Willy.
 
M

Michael A. Covington

Exactly. In the release version of the software, when not running under the
IDE, the variable ceased to exist (and thus the mutex did too) soon after
the last use of the variable. Not instantly, but as soon as there was an
operation that required major rearrangement of memory (opening a new
window).

When debugging under the IDE, the variable didn't cease to exist, because
(of course) the debugger was still using all the variables until they
actually went out of scope.

It turned out that Application.Run() was not the problem. Thanks for your
assistance.
 
W

William Stacey [MVP]

It turned out that Application.Run() was not the problem. Thanks for your
assistance.

It was in my testing. When you don't put the form var in application.run,
nothing get run after the form closes (at least with this simple test).
When you include the form in the run(), then the lines following
application.run ran when closing the form. It seems like it just exits
after form close if form var is not inside the run(). Let me know if you
see other behavior.
 
M

Michael A. Covington

William Stacey said:
It was in my testing. When you don't put the form var in application.run,
nothing get run after the form closes (at least with this simple test).
When you include the form in the run(), then the lines following
application.run ran when closing the form. It seems like it just exits
after form close if form var is not inside the run(). Let me know if you
see other behavior.

Everything I've observed is consistent with what Microsoft says:

Application.Run() starts a message loop on the current thread, so that all
the thread's windows will respond to messages. One of them must eventually
execute Application.Exit(), which will stop the message loop and allow
execution to proceed after the Application.Run().

Application.Run(F) does that, and also makes form F visible, and also
provides form F with a Closing event handler that performs
Application.Exit() when the form is closed.

In my application, I'm going to have either of 2 different "main forms"
depending on how it's launched, so I create and show the form myself, and
provide it the appropriate event handler, and then just do an
Application.Run().

After the Application.Exit() executes, the code after Application.Run() is
executed.
 
M

Michael A. Covington

Thanks to all who helped. I've summarized what I learned at:

www.ai.uga.edu/mc/CSharpNotes.html

www.ai.uga.edu/mc/SingleInstance.html


--

Michael A. Covington - Artificial Intelligence Ctr - University of Georgia

"In the core C# language it is simply not possible to have an uninitialized
variable, a 'dangling' pointer, or an expression that indexes an array
beyond its bounds. Whole categories of bugs that routinely plague C and C++
programs are thus eliminated." - A. Hejlsberg, The C# Programming Language
 
W

William Stacey [MVP]

You could actually do that anyway and keep the Application.Run(form) method.
Your forms derive from base Form. So just create a form var of type Form
and set that to the form ref you want to show. You can put that in
Run(form).

int mybool = true;
Form1 form1 = new Form1();
MyForm myForm = new MyForm();
Form form;
if ( mybool )
form = form1;
else
form = myForm;

//Application.Run(new Form1());
Application.Run(form);
Console.WriteLine("Stuff after.");
 
M

Michael A. Covington

William Stacey said:
You could actually do that anyway and keep the Application.Run(form) method.
Your forms derive from base Form. So just create a form var of type Form
and set that to the form ref you want to show. You can put that in
Run(form).

Except that I don't want one of my forms to have its Closing event tied to
Application.Exit(). The forms are rigged so that if the main menu launches,
its Closing event is tied to Application.Exit(); but if the edit form is
what we choose to launch, then its Closing event brings up the main menu
rather than exiting the app.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top