2009-05-19 119 views
5
ServiceController serviceController = new ServiceController(someService); 
serviceController.Stop(); 
serviceController.WaitForStopped(); 
DoSomething(); 

SomeService在sqlserver文件上工作。 DoSomething()想要複製該SQL文件。如果SomeService沒有完全關閉,它會拋出一個錯誤,因爲數據庫文件仍然被鎖定。在前面提到的代碼中,我通過了WaitForStopped()方法,但是該服務在DoSomething()之後才釋放數據庫文件,因此出現錯誤。ServiceController.Stop()後服務未完全停止()

做了一些更多的調查後,我發現在DoSomething方法調用之前,我發現服務控制器狀態顯示已停止,但正在查看一些ProcMon日誌,該服務在從DoSomething中引發錯誤後釋放數據庫文件。另外,如果我在WaitForStopped和DoSomething方法之間放置一個Thread.Sleep,例如... 5秒鐘,數據庫文件就會釋放並且一切正常。然而,我不是尋求保證的解決方案。

任何想法?

回答

3

ServiceController.WaitForStopped()/ WaitForStatus()將在服務實現聲明它已停止時返回。這並不意味着該流程已經釋放了所有資源並且已經退出。我見過除SQL Server以外的其他數據庫也這樣做。

如果您確實想要確保數據庫完全真正停止,您必須與數據庫本身接口,獲取進程ID並等待它退出,等待文件上的鎖定爲發佈,...

+0

Hrm,這回答了爲什麼。任何想法,但一個很好的解決方法? – Dave 2009-05-19 21:18:16

-4

嘗試使用Environment.Exit(1);

+5

強制結束SQL Server是一個非常糟糕的主意。 – 2011-10-04 19:36:02

0

我之前看到過這個,當時我停止了一個服務,這個服務依賴於另一個服務,而第二個服務持有我甚至不知道它正在使用的資源。你認爲這可能是這樣嗎?我知道SQL有很多不同的組件,但我沒有考慮是否有多個與之關聯的服務。

祝你好運!

17

Windows服務是一個層上的進程;爲了成爲服務,應用程序必須連接到服務控制管理器並宣佈哪些服務可用。此連接在ADVAPI32.DLL庫中處理。建立此連接後,庫維護一個線程,等待來自服務控制管理器的命令,然後該服務控制管理器可以隨意啓動和停止服務。我不相信這個過程需要在最後一個服務終止時退出。雖然這是通常發生的事情,但在最後一個服務進入「已停止」狀態後,與服務控制管理器的鏈接結束可能會在流程實際終止之前發生,並釋放尚未明確釋放的資源。

Windows服務API包含的功能使您可以獲取託管服務的進程的進程ID。一個進程可能承載多個服務,因此當您感興趣的服務終止時,進程可能不會真正退出,但您應該使用SQL Server來保證安全。不幸的是,.NET Framework不公開這個功能。但是,它確實將句柄公開給它在內部用於API調用的服務,並且可以使用它來創建自己的API調用。然後,通過一些P/Invoke,您可以獲得Windows服務進程的進程ID,並從那裏獲得必要的權限,您可以打開一個進程的句柄,用於等待進程出口。

事情是這樣的:

[DllImport("advapi32")] 
static extern bool QueryServiceStatusEx(IntPtr hService, int InfoLevel, ref SERVICE_STATUS_PROCESS lpBuffer, int cbBufSize, out int pcbBytesNeeded); 

const int SC_STATUS_PROCESS_INFO = 0; 

[StructLayout(LayoutKind.Sequential)] 
struct SERVICE_STATUS_PROCESS 
{ 
    public int dwServiceType; 
    public int dwCurrentState; 
    public int dwControlsAccepted; 
    public int dwWin32ExitCode; 
    public int dwServiceSpecificExitCode; 
    public int dwCheckPoint; 
    public int dwWaitHint; 
    public int dwProcessId; 
    public int dwServiceFlags; 
} 

const int SERVICE_WIN32_OWN_PROCESS = 0x00000010; 

const int SERVICE_RUNS_IN_SYSTEM_PROCESS = 0x00000001; 

public static void StopServiceAndWaitForExit(string serviceName) 
{ 
    using (ServiceController controller = new ServiceController(serviceName)) 
    { 
    SERVICE_STATUS_PROCESS ssp = new SERVICE_STATUS_PROCESS(); 
    int ignored; 

    // Obtain information about the service, and specifically its hosting process, 
    // from the Service Control Manager. 
    if (!QueryServiceStatusEx(controller.ServiceHandle.DangerousGetHandle(), SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(ssp), out ignored)) 
     throw new Exception("Couldn't obtain service process information."); 

    // A few quick sanity checks that what the caller wants is *possible*. 
    if (ssp.dwServiceType != SERVICE_WIN32_OWN_PROCESS) 
     throw new Exception("Can't wait for the service's hosting process to exit because there may be multiple services in the process (dwServiceType is not SERVICE_WIN32_OWN_PROCESS"); 

    if ((ssp.dwServiceFlags & SERVICE_RUNS_IN_SYSTEM_PROCESS) != 0) 
     throw new Exception("Can't wait for the service's hosting process to exit because the hosting process is a critical system process that will not exit (SERVICE_RUNS_IN_SYSTEM_PROCESS flag set)"); 

    if (ssp.dwProcessId == 0) 
     throw new Exception("Can't wait for the service's hosting process to exit because the process ID is not known."); 

    // Note: It is possible for the next line to throw an ArgumentException if the 
    // Service Control Manager's information is out-of-date (e.g. due to the process 
    // having *just* been terminated in Task Manager) and the process does not really 
    // exist. This is a race condition. The exception is the desirable result in this 
    // case. 
    using (Process process = Process.GetProcessById(ssp.dwProcessId)) 
    { 
     // EDIT: There is no need for waiting in a separate thread, because MSDN says "The handles are valid until closed, even after the process or thread they represent has been terminated." (http://msdn.microsoft.com/en-us/library/windows/desktop/ms684868%28v=vs.85%29.aspx), so to keep things in the same thread, the process HANDLE should be opened from the process id before the service is stopped, and the Wait should be done after that. 

     // Response to EDIT: What you report is true, but the problem is that the handle isn't actually opened by Process.GetProcessById. It's only opened within the .WaitForExit method, which won't return until the wait is complete. Thus, if we try the wait on the current therad, we can't actually do anything until it's done, and if we defer the check until after the process has completed, it won't be possible to obtain a handle to it any more. 

     // The actual wait, using process.WaitForExit, opens a handle with the SYNCHRONIZE 
     // permission only and closes the handle before returning. As long as that handle 
     // is open, the process can be monitored for termination, but if the process exits 
     // before the handle is opened, it is no longer possible to open a handle to the 
     // original process and, worse, though it exists only as a technicality, there is 
     // a race condition in that another process could pop up with the same process ID. 
     // As such, we definitely want the handle to be opened before we ask the service 
     // to close, but since the handle's lifetime is only that of the call to WaitForExit 
     // and while WaitForExit is blocking the thread we can't make calls into the SCM, 
     // it would appear to be necessary to perform the wait on a separate thread. 
     ProcessWaitForExitData threadData = new ProcessWaitForExitData(); 

     threadData.Process = process; 

     Thread processWaitForExitThread = new Thread(ProcessWaitForExitThreadProc); 

     processWaitForExitThread.IsBackground = Thread.CurrentThread.IsBackground; 
     processWaitForExitThread.Start(threadData); 

     // Now we ask the service to exit. 
     controller.Stop(); 

     // Instead of waiting until the *service* is in the "stopped" state, here we 
     // wait for its hosting process to go away. Of course, it's really that other 
     // thread waiting for the process to go away, and then we wait for the thread 
     // to go away. 
     lock (threadData.Sync) 
     while (!threadData.HasExited) 
      Monitor.Wait(threadData.Sync); 
    } 
    } 
} 

class ProcessWaitForExitData 
{ 
    public Process Process; 
    public volatile bool HasExited; 
    public object Sync = new object(); 
} 

static void ProcessWaitForExitThreadProc(object state) 
{ 
    ProcessWaitForExitData threadData = (ProcessWaitForExitData)state; 

    try 
    { 
    threadData.Process.WaitForExit(); 
    } 
    catch {} 
    finally 
    { 
    lock (threadData.Sync) 
    { 
     threadData.HasExited = true; 
     Monitor.PulseAll(threadData.Sync); 
    } 
    } 
} 
+0

完美的作品,非常感謝! – 2013-04-17 19:56:05

1

在我來說,我已經使用了互操作性展示:

[StructLayout(LayoutKind.Sequential)] 
public struct SC_HANDLE__ 
{ 
    public int unused; 
} 

[Flags] 
public enum SERVICE_CONTROL : uint 
{ 
    STOP = 0x00000001, 
    PAUSE = 0x00000002, 
    CONTINUE = 0x00000003, 
    INTERROGATE = 0x00000004, 
    SHUTDOWN = 0x00000005, 
    PARAMCHANGE = 0x00000006, 
    NETBINDADD = 0x00000007, 
    NETBINDREMOVE = 0x00000008, 
    NETBINDENABLE = 0x00000009, 
    NETBINDDISABLE = 0x0000000A, 
    DEVICEEVENT = 0x0000000B, 
    HARDWAREPROFILECHANGE = 0x0000000C, 
    POWEREVENT = 0x0000000D, 
    SESSIONCHANGE = 0x0000000E 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct SERVICE_STATUS 
{ 

    /// DWORD->unsigned int 
    public uint dwServiceType; 
    /// DWORD->unsigned int 
    public uint dwCurrentState; 
    /// DWORD->unsigned int 
    public uint dwControlsAccepted; 
    /// DWORD->unsigned int 
    public uint dwWin32ExitCode; 
    /// DWORD->unsigned int 
    public uint dwServiceSpecificExitCode; 
    /// DWORD->unsigned int 
    public uint dwCheckPoint; 
    /// DWORD->unsigned int 
    public uint dwWaitHint; 
} 

public class NativeMethods 
{ 
    public const int SC_MANAGER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED 
       | (SC_MANAGER_CONNECT 
       | (SC_MANAGER_CREATE_SERVICE 
       | (SC_MANAGER_ENUMERATE_SERVICE 
       | (SC_MANAGER_LOCK 
       | (SC_MANAGER_QUERY_LOCK_STATUS | SC_MANAGER_MODIFY_BOOT_CONFIG)))))); 

    /// STANDARD_RIGHTS_REQUIRED -> (0x000F0000L) 
    public const int STANDARD_RIGHTS_REQUIRED = 983040; 
    /// SC_MANAGER_CONNECT -> 0x0001 
    public const int SC_MANAGER_CONNECT = 1; 
    /// SC_MANAGER_CREATE_SERVICE -> 0x0002 
    public const int SC_MANAGER_CREATE_SERVICE = 2; 
    /// SC_MANAGER_ENUMERATE_SERVICE -> 0x0004 
    public const int SC_MANAGER_ENUMERATE_SERVICE = 4; 
    /// SC_MANAGER_LOCK -> 0x0008 
    public const int SC_MANAGER_LOCK = 8; 
    /// SC_MANAGER_QUERY_LOCK_STATUS -> 0x0010 
    public const int SC_MANAGER_QUERY_LOCK_STATUS = 16; 
    /// SC_MANAGER_MODIFY_BOOT_CONFIG -> 0x0020 
    public const int SC_MANAGER_MODIFY_BOOT_CONFIG = 32; 
    /// SERVICE_CONTROL_STOP -> 0x00000001 
    public const int SERVICE_CONTROL_STOP = 1; 
    /// SERVICE_QUERY_STATUS -> 0x0004 
    public const int SERVICE_QUERY_STATUS = 4; 
    public const int GENERIC_EXECUTE = 536870912; 
    /// SERVICE_RUNNING -> 0x00000004 
    public const int SERVICE_RUNNING = 4; 

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); 

    [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW")] 
    public static extern IntPtr OpenSCManagerW(
     [In()] [MarshalAs(UnmanagedType.LPWStr)] string lpMachineName, 
     [In()] [MarshalAs(UnmanagedType.LPWStr)] string lpDatabaseName, 
     uint dwDesiredAccess); 

    [DllImport("advapi32.dll", SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    public static extern bool ControlService(IntPtr hService, SERVICE_CONTROL dwControl, ref SERVICE_STATUS lpServiceStatus); 

    [DllImport("advapi32.dll", SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    public static extern bool CloseServiceHandle(IntPtr hSCObject); 

    [DllImport("advapi32.dll", EntryPoint = "QueryServiceStatus", CharSet = CharSet.Auto)] 
    public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS dwServiceStatus); 

    [SecurityCritical] 
    [HandleProcessCorruptedStateExceptions] 
    public static void ServiceStop() 
    { 
     IntPtr manager = IntPtr.Zero; 
     IntPtr service = IntPtr.Zero; 
     SERVICE_STATUS status = new SERVICE_STATUS(); 

     if ((manager = OpenSCManagerW(null, null, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero) 
     { 
      if ((service = OpenService(manager, Resources.ServiceName, SC_MANAGER_ALL_ACCESS)) != IntPtr.Zero) 
      { 
       QueryServiceStatus(service, ref status); 
      } 

      if (status.dwCurrentState == SERVICE_RUNNING) 
      { 
       int i = 0; 
       //not the best way, but WaitStatus didnt work correctly. 
       while (i++ < 10 && status.dwCurrentState != SERVICE_CONTROL_STOP) 
       { 
        ControlService(service, SERVICE_CONTROL.STOP, ref status); 
        QueryServiceStatus(service, ref status); 
        Thread.Sleep(200); 
       } 

      } 
     } 


     if (manager != IntPtr.Zero) 
     { 
      var b = CloseServiceHandle(manager); 
     } 

     if (service != IntPtr.Zero) 
     { 
      var b = CloseServiceHandle(service); 
     } 
    } 
}