【问题标题】:Mutex not releasing互斥锁不释放
【发布时间】:2011-03-17 13:46:36
【问题描述】:

我的 c# WinForm 解决方案包含多个项目,包括一个包含 frmAdmin 的管理项目和一个包含 frmUser 的用户项目。第三个项目包含 frmTimer,它有一个定期启动 frmUser 的计时器。

我希望 frmTimer 在打开 frmAdmin 时不启动 frmUser。

我正在使用一个命名的互斥锁来告诉 frmTimer frmAdmin 是否打开;但是,在 frmAdmin 关闭后,互斥锁似乎没有被释放。

互斥锁在 frmAdmin 中创建,代码如下:

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>>
}

最初,我在 frmAdmin_FormClosing 方法中没有任何互斥代码(该方法仅包含 Application.Exit() 行)。我添加了互斥锁代码试图释放互斥锁,但它仍然没有被释放。

在frmTimer中使用互斥锁是这样的:

    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>>
        }
    }

当我运行应用程序时,每次计时器触发时都会出现带有“调试 4”文本的消息框,直到我打开 frmAdmin(密码验证后从 frmLogin 启动 frmAdmin),然后在带有“调试 3”的消息框上每次计时器触发时都会出现文本,即使在我退出 frmAdmin 之后也是如此。退出 frmAdmin 时,我看到带有“调试 2”文本的消息框。我从未见过带有“Debug 1”文本的消息框(或输出窗口消息)。

看起来好像在 frmAdmin 关闭后互斥锁没有释放,这会阻止 frmUser 启动。

感谢任何帮助。

这是this 问题的后续问题。

更新

这是我运行后的代码。由于 Hans Passant 和 Chris Taylor 以及来自 this 帖子的 Serhio 的回答,我得到了它的工作。

现在在 frmAdmin 中使用如下代码创建互斥锁:

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

在frmTimer中使用互斥锁是这样的:

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>>
    }
}

【问题讨论】:

    标签: c# winforms mutex


    【解决方案1】:

    问题出在 Elapsed 事件处理程序中,它使用 Mutex.OpenExisting() 检查互斥锁是否存在。当然存在。您实际上并没有检查它是否已发出信号。这需要调用它的 WaitOne(0) 方法。

    还要注意在 Timer.Elapsed 事件中创建表单是非常不合适的。该事件运行一个线程池线程,它根本不适合充当 UI 线程。它具有错误的 COM 状态([STAThread] 和 Thread.SetApartmentState),这是您无法在线程池线程上更改的属性。请改用常规的 Form.Timer,以便在程序的 UI 线程上创建表单。

    编辑:还要注意不可避免的竞争,计时器可能会在管理员表单关闭前一微秒创建用户表单。换句话说,您将拥有一个没有管理员表单的用户表单,这是您编写此代码要防止的一个条件。这合适吗?尝试不同流程中的表单相互影响是个坏主意……

    【讨论】:

    • 汉斯您好,感谢您的回复。作为后续,我有另一个互斥锁,用于确保应用程序只启动一次。这会影响我对 WaitOnce(0) 的使用吗? WaitOne(0) 如何知道要查找哪个互斥锁?
    • 不要忽略Mutex.OpenExisting()的返回值,它就是你要找的互斥量。实际上,不需要发出互斥锁的信号,只要存在就足够了。请务必调用 Close() 方法。检查此线程:stackoverflow.com/questions/1904519/…
    • 跟进您关于使用 Timer.Elapsed 事件创建新表单的警告:我的响应是否应该简单地更改:private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { 像这样: private void timer1_Tick(object sender, System.EventArgs e) { 然后还添加这两行: private System.Windows.Form.Timer timer1; this.timer1.Tick += new System.EventHandler(this.timer1_Tick);如果答案很复杂,我可以将其作为新问题发布。
    【解决方案2】:

    问题在于,一旦您运行管理应用程序,Mutex 就会存在,然后 OpenExisting 就会成功。释放 Mutex 不会破坏 Kernel 对象,它只是释放对 mutex 的持有,以便其他等待线程可以执行。因此后续的 Mutex.OpenExisting 调用成功打开了互斥锁。

    如果您成功打开互斥锁,并且如果 WaitOne 返回 false,那么您可能想要使用 Mutex.WaitOne(TimeSpan),那么您知道您无法获取互斥锁,因此管理应用程序仍然持有互斥锁。

    【讨论】:

    • 谢谢克里斯,你的回答和汉斯都同意。我会实施它。再次感谢。
    • 你好克里斯,如果你还在,你能告诉我如何销毁互斥锁​​的内核对象吗?我已经尝试过 mutex.Close(),但似乎没有这样做。
    • @Frederick,内核对象只有在所有对它的引用都被释放时才会关闭。因此,在您的示例中,您的管理应用程序创建一个引用,然后当 OpenExisting 打开互斥锁时,这是另一个引用,因此两者都需要在内核对象被销毁之前关闭。
    • 这些是我需要听到的神奇词汇。关闭两种形式的引用使其一切正常。
    • @Frederick,很高兴有帮助!
    猜你喜欢
    • 1970-01-01
    • 2010-12-25
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-21
    • 2017-02-23
    • 2013-06-08
    相关资源
    最近更新 更多