已放弃的命名信号量未释放

本文关键字:信号量 释放 放弃 | 更新日期: 2023-09-27 18:24:27

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

对于命名互斥,在这种情况下会引发一个废弃互斥异常,但对于信号量则不会。当另一程序实例提前终止时,如何防止一个程序实例停滞?

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");
    }
}

已放弃的命名信号量未释放

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

原因是.NET信号量对象所基于的Windows信号量对象没有跟踪哪些线程已经获取了它,因此不能抛出类似于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软件包感兴趣。我写了三篇关于这些东西的文章,最后两篇仍然可以在DevSource上找到。我不知道第一个发生了什么。

  • .NET中的控制台输入
  • 在.NET中使用控制台屏幕缓冲区

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

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

或者在您的try''chutch 中添加一个finally pharse

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

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

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