tags:

views:

69

answers:

2

My c# WinForm solution contains several projects including an Admin project containing frmAdmin and a User project containing frmUser. A third project contains frmTimer that has a timer that periodically launches frmUser.

I want frmTimer to not launch frmUser when frmAdmin is open.

I'm using a named mutex to tell frmTimer if frmAdmin is open; however, the mutex appears not to be released after frmAdmin is closed.

The mutex is created in frmAdmin with code like this:

public partial class frmAdmin : Form
{
    Mutex m;
    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        m = new Mutex(true, "frmAdmin");
    }
    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        m.ReleaseMutex();
        MessageBox.Show("Debug 1 -- In the frmAdmin ONCLOSED Event.");  //test code
        Debug.WriteLine("Debug 1 -- In the frmAdmin ONCLOSED Event.");  //test code
  }

    public frmAdmin(string strPassedFromLogin)
    {
        InitializeComponent();
        <<Code snipped>>
             }

    private void frmAdmin_FormClosing(object sender, FormClosingEventArgs e)
    {
        //Start _ Added
        bool mutexSet = true;
        try
        {
            Mutex.OpenExisting("frmAdmin");
            MessageBox.Show("Debug 2 -- In the frmAdmin FORMCLOSING Event.");  //test code
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            mutexSet = false;
        }
        if (mutexSet)
        {
            base.OnClosed(e);
            m.ReleaseMutex();
        }
        //End _ Added

        Application.Exit();
    }

    <<Code snipped>>
}

Initially, I did not have any mutex code in the frmAdmin_FormClosing method (the method only contained the Application.Exit() line). I added the mutex code in an attempt to release the mutex, but it is still not being released.

The mutex is used in frmTimer like this:

    private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        bool adminIsOpen = true;
        try
        {
            Mutex.OpenExisting("frmAdmin");
            MessageBox.Show("Debug 3 -- Mutex exists: frmAdmin IS open.");  //test code
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            adminIsOpen = false;
            MessageBox.Show("Debug 4 -- Mutex doesn't exists: frmAdmin is NOT open.");  //test code
        }

        if (adminIsOpen == false)
        {
          //frmAdmin is closed; go ahead and open frmUser.
            <<Code snipped>>
        }
    }

When I run the application, the messagebox with the 'Debug 4' text appears each time the timer fires until I open frmAdmin (frmAdmin is launched from frmLogin after password verification), from then on the messagebox with the 'Debug 3' text appears each time the timer fires, even after I exit frmAdmin. When exiting frmAdmin, I see the messagebox with the 'Debug 2' text. I've never seen the messagebox (or an output window message) with the 'Debug 1' text.

It appears as though the mutex doesn't release after frmAdmin is closed and this prevents frmUser from launching.

Any help is appreciated.

This is a follow-up question to this question.

UPDATE

Here is my code after getting it to work. I got it to work because of the answers from Hans Passant and Chris Taylor and from Serhio from this post.

The mutex is now created in frmAdmin with code like this:

    Mutex m;
    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        m = new Mutex(true, "frmAdmin");
    }

    //This 'OnClosed' event is skipped when this application is terminated using only Exit(); therefore, call Close() before calling Exit().
    //The 'catch' code is added to insure the program keeps running in the event these exceptions occur.
    protected override void OnClosed(EventArgs e)
    {
        if (m != null)
        {
            try
            {
                base.OnClosed(e);
                m.ReleaseMutex();
                m.Close(); 
            }
            catch (AbandonedMutexException)
            {
                //This catch is included to insure the program keeps running in the event this exception occurs.
            }
            catch (ApplicationException)
            {
                //This catch is included to insure the program keeps running in the event this exception occurs.
            }
            catch (SynchronizationLockException)
            {
                //This catch is included to insure the program keeps running in the event this exception occurs.
            }
        }
    }

The mutex is used in frmTimer like this:

private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    bool adminIsOpen = false;
    Mutex _muty = null;
    try
     {
        //If the named mutex does not exist then OpenExisting will throw the 'WaitHandleCannotBeOpenedException',
        //otherwise the mutex exists and Admin is open.
        _muty = Mutex.OpenExisting("frmAdmin");
        adminIsOpen = true;
        _muty.Close();
    }
    catch (WaitHandleCannotBeOpenedException)
    {
        //This catch is thrown when Admin is not opened (keep 'adminIsOpen = false'). Do not delete this catch.
    }
    catch (AbandonedMutexException)
    {
        //This catch is included to insure the program keeps running in the event this exception occurs.
    }

    if (adminIsOpen == false)
    {
        //frmAdmin is closed; go ahead and open frmUser.
        <<Code snipped>>
    }
}
+3  A: 

The problem is that once you run the admin application the Mutex exists and then the OpenExisting succeeds. Releasing a Mutex does not destroy the Kernel object, it just releases the hold on the mutex so that other waiting threads can execute. Therefore susequent Mutex.OpenExisting calls open the mutex successfully.

You probably want to use Mutex.WaitOne(TimeSpan) if you successfully open the Mutex and if WaitOne returns false then you know you you could not acquire the mutex so the Admin application still hold the mutex.

Chris Taylor
Thank you Chris, both your answer and Hans' agree. I will implement it. Again, thanks.
Frederick
Hello Chris, if you're still there, can you tell me how to destroy the mutex's Kernel object? I've tried mutex.Close(), but that doesn't seem to do it.
Frederick
@Frederick, the kernel object will only be closed when all references to it are released. So in your example, your admin application creates a reference and then when the OpenExisting opens the mutex, that is another reference, so both will need to close before the kernel object is destroyed.
Chris Taylor
@Chris, those were the magic words I needed to hear. Closing the reference in both forms made it everything work.
Frederick
@Frederick, glad that helped!
Chris Taylor
+2  A: 

The problem is in the Elapsed event handler, it checks if the mutex exists with Mutex.OpenExisting(). Sure it exists. You are not actually checking if it is signaled. That takes calling its WaitOne(0) method.

Also beware that creating a form in a Timer.Elapsed event is quite inappropriate. That event runs a threadpool thread, it is not at all suitable to act as a UI thread. It has the wrong COM state ([STAThread] and Thread.SetApartmentState), a property you cannot change on a threadpool thread. Use a regular Form.Timer instead so that the form gets created on program's UI thread.

Edit: also beware of the inevitable race, the timer could create the User form one microsecond before the Admin form closes. In other words, you'll have a User form without an Admin form, the one condition you wrote this code to prevent. Is that appropriate? Trying forms in different processes to affect each other is a bad idea...

Hans Passant
Hello Hans, Thank you for responding. As a follow-up, I have another mutex that is used to insure that the application is launched only once. Will this effect my use of WaitOnce(0)? How does WaitOne(0) know which mutex to look for?
Frederick
Don't ignore the return value of Mutex.OpenExisting(), it is the mutex you are looking for. Actually signaling the mutex isn't necessary, just existence is enough. Be sure to call the Close() method. Check this thread: http://stackoverflow.com/questions/1904519/how-to-call-win32-createmutex-from-net
Hans Passant
@Hans, to follow-up on your warning about using a Timer.Elapsed event to create a new form: Should my response be simply to change: private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { To something like this: private void timer1_Tick(object sender, System.EventArgs e) { And then also add these two lines: private System.Windows.Form.Timer timer1; this.timer1.Tick += new System.EventHandler(this.timer1_Tick); If the answer is complex, I can post it as a new question.
Frederick
That should be fine.
Hans Passant