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

SomeService hoạt động trên tệp sqlserver. DoSomething() muốn sao chép tệp SQL đó. Nếu SomeService không được đóng hoàn toàn, nó sẽ ném một lỗi vì tệp cơ sở dữ liệu vẫn bị khóa. Trong đoạn mã nói trên, tôi vượt qua phương thức WaitForStopped() và dịch vụ không giải phóng tệp cơ sở dữ liệu cho đến sau DoSomething(), do đó tôi gặp lỗi. Thực hiện một số điều tra thêm, tôi thấy rằng trước cuộc gọi phương thức DoSomething tôi thấy trạng thái bộ điều khiển dịch vụ hiển thị dừng và chưa xem xét một số bản ghi dịch vụ của bản ghi dịch vụ phát hành tệp cơ sở dữ liệu sau khi tôi bị lỗi do DoSomething ném.Dịch vụ không được dừng hoàn toàn sau ServiceController.Stop()

Ngoài ra, nếu tôi đặt một Thread.Sleep giữa WaitForStopped và phương pháp DoSomething cho biết ... 5 giây, tập tin cơ sở dữ liệu được phát hành và tất cả là tốt. Không phải là giải pháp của surety tôi đang tìm kiếm tuy nhiên.

Bất kỳ ý tưởng nào?

Trả lời

3

ServiceController.WaitForStopped()/WaitForStatus() sẽ trở lại sau khi triển khai dịch vụ xác nhận quyền sở hữu đã dừng. Điều này không cần thiết có nghĩa là quá trình đã phát hành tất cả các tài nguyên của nó và đã thoát. Tôi đã nhìn thấy cơ sở dữ liệu khác hơn là SQL Server làm điều này là tốt.

Nếu bạn thực sự muốn chắc chắn rằng cơ sở dữ liệu bị dừng hoàn toàn và thực sự, bạn sẽ phải giao tiếp với cơ sở dữ liệu, nhận được một id của quá trình và đợi nó thoát ra, chờ khóa trên các tệp đã phát hành, ...

+0

Hrm, câu trả lời này là lý do tại sao. Bất kỳ ý tưởng về một workaround tốt mặc dù? – Dave

-4

Hãy thử sử dụng Environment.Exit (1);

+5

ép buộc chấm dứt SQL Server là một ý kiến ​​tồi. –

0

Tôi đã thấy điều này trước khi dừng dịch vụ phụ thuộc vào dịch vụ khác và dịch vụ thứ hai đang giữ tài nguyên mà tôi thậm chí không biết nó đang sử dụng. Bạn có nghĩ đây có phải là trường hợp không? Tôi biết SQL có khá nhiều thành phần khác nhau, nhưng tôi chưa xem xét liệu có nhiều dịch vụ liên kết với nó hay không.

Chúc may mắn!

17

Dịch vụ Windows là một lớp trên cùng của các quy trình; để trở thành dịch vụ, ứng dụng phải kết nối với Trình quản lý dịch vụ và thông báo dịch vụ nào có sẵn. Kết nối này được xử lý trong thư viện ADVAPI32.DLL. Khi kết nối này được thiết lập, thư viện sẽ duy trì một luồng đang đợi các lệnh từ Service Control Manager, sau đó có thể bắt đầu và dừng các dịch vụ một cách tùy ý. Tôi không tin rằng quá trình này là cần thiết để thoát khi dịch vụ cuối cùng chấm dứt. Mặc dù đó là những gì thường xảy ra, kết thúc của liên kết với Service Control Manager, xuất hiện sau khi dịch vụ cuối cùng vào trạng thái "Ngưng", có thể xảy ra đáng kể trước khi quá trình thực sự chấm dứt, giải phóng mọi tài nguyên mà nó chưa được phát hành rõ ràng .

API dịch vụ Windows bao gồm chức năng cho phép bạn lấy ID tiến trình của quá trình đang lưu trữ dịch vụ. Có thể cho một quá trình duy nhất để lưu trữ nhiều dịch vụ và do đó quá trình có thể không thực sự thoát khi dịch vụ mà bạn quan tâm đã chấm dứt, nhưng bạn nên an toàn với SQL Server. Thật không may, các.NET Framework không tiếp xúc với chức năng này. Tuy nhiên, nó cho phép xử lý dịch vụ mà nó sử dụng nội bộ cho các cuộc gọi API và bạn có thể sử dụng nó để thực hiện các cuộc gọi API của riêng bạn. Với một chút P/Invoke, sau đó, bạn có thể lấy ID tiến trình của quy trình Dịch vụ Windows và từ đó, với điều kiện bạn có quyền cần thiết, bạn có thể mở một xử lý cho quá trình có thể được sử dụng để đợi nó lối thoát.

Something như thế này:

[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

Hoạt động hoàn hảo, cảm ơn rất nhiều! –

1

Trong trường hợp của tôi, tôi đã sử dụng interops:

[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); 
     } 
    } 
} 
Các vấn đề liên quan