Покинутый названный семафор не выпущен - PullRequest
4 голосов
/ 13 февраля 2012

когда программа на 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");
    }
}

Ответы [ 2 ]

4 голосов
/ 13 февраля 2012

В общем, вы не можете гарантировать, что поток выпускает семафор при выходе из потока.Вы можете написать блоки try / finally и критические финализаторы, но они не всегда будут работать, если программа завершится ненормально.И, в отличие от мьютексов, другие потоки не будут уведомлены, если поток завершается, пока он все еще содержит семафор.

Причина в том, что объект семафора Windows , на котором основан объект .NET Semaphore, не отслеживает, какие потоки его получили, и поэтому не может выдатьисключение аналогично 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.Я не знаю, что случилось с первым.

2 голосов
/ 13 февраля 2012

Вы можете написать mySemaphore.Release (); в классе деструктор

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

или добавьте, наконец, pharse к вашей попытке \ catch

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

Если вы используете asp.net, вы также можете использовать Application_End, который находится в Global.asax.cs

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