when a C# program holds a named semaphore, it does not seem to be released when the application is terminated early (for example by pressing Ctrl+C or closing the console window). At least not until all instances of the process have terminated.
With a named mutex an AbandonedMutexException is raised in this case but not with a semaphore. How do you prevent one program instance from stalling when another program instance has been terminated early?
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");
}
}
In general, you can't guarantee that a thread releases a semaphore when the thread exits. You can write try/finally blocks and critical finalizers, but those won't always work if the program terminates abnormally. And, unlike mutexes, other threads won't be notified if a thread exits while it still holds the semaphore.
The reason is that the Windows semaphore object, on which the .NET Semaphore object is based, doesn't keep track of which threads have acquired it, and therefore can't throw an exception similar to the AbandonedMutexException
.
That said, you can be notified when the user closes the window. You need to set a control handler to listen to particular events. You call the Windows API function SetConsoleCtrlHandler, passing it a callback function (delegate) that handles the events you're interested in. It's been a while since I did this, but in general.
Create a managed prototype for the SetConsoleCtrlHandler
function, and the callback:
/// <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);
Now, create your handler method:
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;
}
And, finally, during initialization you want to set the control handler:
SetConsoleCtrlHandler(ConsoleControlHandler);
Your control handler will now be called when the user closes the window. That will allow you to release the semaphore or do other cleanup.
You might be interested in my ConsoleDotNet package. I wrote three articles about this stuff, the last two of which are still available at DevSource. I don't know what happened to the first one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With