This article actually touches up on some advanced topics of C#, and some things that you may not have ever come across. MSDN has this to say about threads:
An operating-system ThreadId has no fixed relationship to a managed thread, because an unmanaged host can control the relationship between managed and unmanaged threads. Specifically, a sophisticated host can use the CLR Hosting API to schedule many managed threads against the same operating system thread, or to move a managed thread between different operating system threads.
What this is really trying to explain is thread affinity, and that you are not guaranteed to have a native thread map 1-to-1 to a managed thread depending on the CLR (Common Language Runtime) that is hosting your code. This is important to know when you P/Invoke into native functions that require calling back into your C# code after a period of time (such as NotifyServiceStatusChange). We want to maintain that 1-to-1 relationship using Thread.BeginThreadAffinity() because the marshaling layer needs to have a valid callback reference at all times.
Here is the code that you can use to P/Invoke NotifyServiceStatusChange in C# in order to wait for a service to stop:
using System; using System.Runtime.InteropServices; using System.Threading; class ServiceAssistant { [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public class SERVICE_NOTIFY { public uint dwVersion; public IntPtr pfnNotifyCallback; public IntPtr pContext; public uint dwNotificationStatus; public SERVICE_STATUS_PROCESS ServiceStatus; public uint dwNotificationTriggered; public IntPtr pszServiceNames; }; [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public struct SERVICE_STATUS_PROCESS { public uint dwServiceType; public uint dwCurrentState; public uint dwControlsAccepted; public uint dwWin32ExitCode; public uint dwServiceSpecificExitCode; public uint dwCheckPoint; public uint dwWaitHint; public uint dwProcessId; public uint dwServiceFlags; }; [DllImport("advapi32.dll")] static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); [DllImport("advapi32.dll")] static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess); [DllImport("advapi32.dll")] static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer); [DllImport("kernel32.dll")] static extern uint SleepEx(uint dwMilliseconds, bool bAlertable); [DllImport("advapi32.dll")] static extern bool CloseServiceHandle(IntPtr hSCObject); delegate void StatusChangedCallbackDelegate(IntPtr parameter); /// <summary> /// Block until a service stops, is killed, or is found to be already dead. /// </summary> /// <param name="serviceName">The name of the service you would like to wait for.</param> /// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param> public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue) { // Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread. Thread.BeginThreadAffinity(); GCHandle notifyHandle = default(GCHandle); StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent; IntPtr hSCM = IntPtr.Zero; IntPtr hService = IntPtr.Zero; try { hSCM = OpenSCManager(null, null, (uint)0xF003F); if (hSCM != IntPtr.Zero) { hService = OpenService(hSCM, serviceName, (uint)0xF003F); if (hService != IntPtr.Zero) { SERVICE_NOTIFY notify = new SERVICE_NOTIFY(); notify.dwVersion = 2; notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate); notify.ServiceStatus = new SERVICE_STATUS_PROCESS(); notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned); IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject(); NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure); SleepEx(timeout, true); } } } finally { // Clean up at the end of our operation, or if this thread is aborted. if (hService != IntPtr.Zero) { CloseServiceHandle(hService); } if (hSCM != IntPtr.Zero) { CloseServiceHandle(hSCM); } // Keep our callback method around until it is called (until this line of code). GC.KeepAlive(changeDelegate); if (notifyHandle != default(GCHandle)) { notifyHandle.Free(); } Thread.EndThreadAffinity(); } } static void ReceivedStatusChangedEvent(IntPtr parameter) { // Do nothing. } }
Its so simple, that it can just be called as follows:
ServiceAssistant.WaitForServiceToStop("YourWindowsServiceName");
Note that this is significantly different from the WaitForStatus method that is available to you out of the box in C#, because the WaitForStatus method polls every 250ms between status checks according to the remarks, whereas NotifyServiceStatusChange is event-driven and subscribes to that particular event (so its less overhead in terms of CPU usage).