2009-05-19 87 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); 
     } 
    } 
}