c# - ServiceController.Stop() 后服务未完全停止

标签 c# .net sql-server service

ServiceController serviceController = new ServiceController(someService);
serviceController.Stop();
serviceController.WaitForStopped();
DoSomething();

SomeService 在 sqlserver 文件上工作。 DoSomething() 想要复制那个 SQL 文件。如果 SomeService 没有完全关闭,它将抛出错误,因为数据库文件仍处于锁定状态。在上述代码中,我通过了 WaitForStopped() 方法,但服务直到 DoSomething() 之后才释放数据库文件,因此出现错误。

进行更多调查后,我发现在调用 DoSomething 方法之前,我看到服务 Controller 状态显示已停止,但查看一些 ProcMon 日志时,服务会在我从 DoSomething 中抛出错误后释放数据库文件。

另外,如果我在 WaitForStopped 和 DoSomething 方法之间放置一个 Thread.Sleep 比如说... 5 秒,数据库文件就会被释放,一切都很好。然而,这不是我正在寻找的保证解决方案。

有什么想法吗?

最佳答案

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_INTERACTIVE_PROCESS = 0x00000100;

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_INTERACTIVE_PROCESS) != 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);
    }
  }
}

关于c# - ServiceController.Stop() 后服务未完全停止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/884853/

相关文章:

c# - 如何更改母版页导航栏中事件页面的链接颜色。网站

c# - 为什么 Nullable<T> HasValue 属性不会在 Null 上抛出 NullReferenceException?

sql - 如何优化 BINARY(N) 的 SQL 查询?

sql-server - 将多个参数添加到 sqlpackage.exe 中的 "Variables"参数的语法是什么?

c# - 让一个方法在另一个方法中返回一个字符串是不好的做法吗?

c# - 在 ASP.Net 中使用 Google Checkout

c# - 平均特定数字的随机数

c# - .Net Core、Portable、Standard、Compact、UWP 和 PCL 之间的区别?

c# - XmlDocument 存在于哪个程序集中,以及如何使用它?

sql-server - SQL 过程没有参数并且提供了参数