【问题标题】:Abandoned named semaphore not released废弃的命名信号量未释放
【发布时间】:2012-03-04 23:09:11
【问题描述】:

当一个 C# 程序持有一个命名信号量时,它似乎不会在应用程序提前终止时被释放(例如通过按 Ctrl+C 或关闭控制台窗口)。至少在进程的所有实例都终止之前不会。

在这种情况下,使用命名互斥体会引发 AbandonedMutexException,但不会引发信号量。当另一个程序实例提前终止时,如何防止一个程序实例停止?

class Program
{
    // Same with count > 1
    private static Semaphore mySemaphore = new Semaphore(1, 1, "SemaphoreTest");

    static void Main(string[] args)
    {
        try
        {
            // Blocks forever if the first process was terminated
            // before it had the chance to call Release
            Console.WriteLine("Getting semaphore");
            mySemaphore.WaitOne();  
            Console.WriteLine("Acquired...");
        }
        catch (AbandonedMutexException)
        {
            // Never called!
            Console.WriteLine("Acquired due to AbandonedMutexException...");
        }
        catch (System.Exception ex)
        {
            Console.WriteLine(ex);
        }

        Thread.Sleep(20 * 1000);
        mySemaphore.Release();
        Console.WriteLine("Done");
    }
}

【问题讨论】:

  • 信号量没有所有者。没有信号量放弃这样的事情。如果要在线程退出时自动释放,请使用互斥锁。
  • @George:查看我的更新答案,它向您展示了如何在用户关闭窗口时收到通知。
  • 当您像这样进行互操作时,您永远不能忽略进程中止。很少有继续运行有意义的情况,很多隐含的状态已经消失。用 Process.Exited 事件打破僵局。

标签: c# .net semaphore


【解决方案1】:

通常,您不能保证线程退出时释放信号量。您可以编写 try/finally 块和关键终结器,但如果程序异常终止,这些将不会总是有效。而且,与互斥锁不同,如果一个线程在仍然持有信号量的情况下退出,则不会通知其他线程。

原因是.NET Semaphore 对象所基于的Windows semaphore object 不跟踪哪些线程已获取它,因此无法抛出类似于AbandonedMutexException 的异常。

也就是说,您可以在用户关闭窗口时收到通知。您需要设置一个控制处理程序来监听特定事件。您调用 Windows API 函数 SetConsoleCtrlHandler,向它传递一个回调函数(委托)来处理您感兴趣的事件。我已经有一段时间没有这样做了,但总的来说。

SetConsoleCtrlHandler 函数和回调创建托管原型:

/// <summary>
/// Control signals received by the console control handler.
/// </summary>
public enum ConsoleControlEventType: int
{
    /// <summary>
    /// A CTRL+C signal was received, either from keyboard input or from a
    /// signal generated by the GenerateConsoleCtrlEvent function.
    /// </summary>
    CtrlC = 0,
    /// <summary>
    /// A CTRL+BREAK signal was received, either from keyboard input or from
    /// a signal generated by GenerateConsoleCtrlEvent.
    /// </summary>
    CtrlBreak = 1,
    /// <summary>
    /// A signal that the system sends to all processes attached to a console
    /// when the user closes the console (either by clicking Close on the console
    /// window's window menu, or by clicking the End Task button command from
    /// Task Manager).
    /// </summary>
    CtrlClose = 2,
    // 3 and 4 are reserved, per WinCon.h
    /// <summary>
    /// A signal that the system sends to all console processes when a user is logging off. 
    /// </summary>
    CtrlLogoff = 5,
    /// <summary>
    /// A signal that the system sends to all console processes when the system is shutting down. 
    /// </summary>
    CtrlShutdown = 6
}

/// <summary>
/// Control event handler delegate.
/// </summary>
/// <param name="CtrlType">Control event type.</param>
/// <returns>Return true to cancel the control event.  A return value of false
/// will terminate the application and send the event to the next control
/// handler.</returns>
public delegate bool ConsoleCtrlHandlerDelegate(ConsoleControlEventType CtrlType);

[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool SetConsoleCtrlHandler(
ConsoleCtrlHandlerDelegate HandlerRoutine,
bool Add);

现在,创建您的处理程序方法:

private static bool ConsoleCtrlHandler(ConsoleControlEventType CtrlType)
{
    switch (CtrlType)
    {
        case CtrlClose:
            // handle it here
            break;
        case CtrlBreak:
            // handle it here
            break;
    }
    // returning false ends up calling the next handler
    // returning true will prevent further handlers from being called.
    return false;
}

最后,在初始化期间,您要设置控制处理程序:

SetConsoleCtrlHandler(ConsoleControlHandler);

当用户关闭窗口时,您的控件处理程序将被调用。这将允许您释放信号量或进行其他清理。

您可能对我的ConsoleDotNet package 感兴趣。我写了三篇关于这个东西的文章,最后两篇仍然可以在 DevSource 上找到。我不知道第一个发生了什么。

【讨论】:

  • 优秀的答案。奇迹般有效。非常感谢!
  • 99.999% 的情况下,关键终结器是在正常(和意外)终止期间清理内核对象的“适当”机制。在另外 0.001% 中,您可能需要重新启动、重新格式化或更换有问题的计算机,而不是为“不稳定的环境”“防御性地编码”。 msdn.microsoft.com/en-us/library/…
【解决方案2】:

您可以编写 mySemaphore.Release();在类析构函数中

class Program
{
    ~Program()  // destructor
    {
        mySemaphore.Release();
    }
}

或在 try\catch 中添加 finally 短语

try{}
catch{}
finally 
{
    mySemaphore.Release();
}

如果您使用的是 asp.net,您也可以使用位于 Global.asax.cs 中的 Application_End

protected void Application_End(Object sender, EventArgs eventArgs)
{
    mySemaphore.Release();
}

【讨论】:

  • 基本上:不要忽略退出路径,而是在停下时清理。
  • 不幸的是,这些都没有帮助。没有 Program 的实例,即使有,当您关闭控制台窗口或按 Ctrl+C 时,也不会调用析构函数。只有在终止程序时 Thread.Sleep 会抛出异常时才会执行 finally 块,但它不会。我也不使用 ASP.net。我也尝试过 Console.CancelKeyPress,它适用于 Ctrl+C,但不适用于关闭窗口。 AppDomain.CurrentDomain.ProcessExit 也没有被调用。
  • 终结器(不是 dtors)不能保证执行。
猜你喜欢
  • 2012-08-01
  • 2012-10-20
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2020-06-01
相关资源
最近更新 更多