C# 停止时服务偶尔挂起:挂起的线程

C# 停止时服务偶尔挂起:挂起的线程,c#,debugging,service,garbage-collection,interop,C#,Debugging,Service,Garbage Collection,Interop,我在C#targeting.NET4.0中编写了一个Windows服务,当我试图停止该服务时,它会在偶尔的情况下完全挂起。通过查看转储文件,我注意到我的许多线程被挂起,尽管我自己在代码中没有挂起它们 环境是Windows Server 2008R2 64位,但我在Windows 7 64位上观察到了相同的挂起。NET 4.0是安装的最新版本 这里有很多代码,所以我只是发布了一些相关的代码片段,如果需要,我可以发布更多 基本设计: Main()启动一个新线程来处理文件的日志记录(其代码在单独的dl

我在C#targeting.NET4.0中编写了一个Windows服务,当我试图停止该服务时,它会在偶尔的情况下完全挂起。通过查看转储文件,我注意到我的许多线程被挂起,尽管我自己在代码中没有挂起它们

环境是Windows Server 2008R2 64位,但我在Windows 7 64位上观察到了相同的挂起。NET 4.0是安装的最新版本

这里有很多代码,所以我只是发布了一些相关的代码片段,如果需要,我可以发布更多

基本设计:

Main()启动一个新线程来处理文件的日志记录(其代码在单独的dll中),然后启动服务

public static void Main(string[] args)
{
    ...
    else if (Args.RunService)
    {
        Logger.Options.LogToFile = true;
        MSPO.Logging.Logger.Start();
        RunService();
        MSPO.Logging.Logger.Stop();
    }
    ...
}

private static void RunService()
{
    service = new ProcessThrottlerService();
    System.ServiceProcess.ServiceBase.Run(service);
}
该线程将保持在那里,直到ServiceBase.Run返回

服务中的OnStart()创建一个新线程并启动它

protected override void OnStart(string[] args)
{
    serviceThread = new MainServiceThread();
    serviceThread.StartThread();
    base.OnStart(args);
}
我创建了一个ManualResetEventSlim,用作程序其余部分的停止信号。OnStop()设置事件

protected override void OnStop()
{
    if (serviceThread != null)
    {
        serviceThread.StopThread(); // Event is signalled in there
        serviceThread.WaitForThreadToReturn(); // This calls thread.Join() on the MainServiceThread thread
    }
    base.OnStop();
}
private void StartHandlerAndWaitForServiceStop()
{
    processHandler.Start(serviceStopEvent);
    serviceStopEvent.Wait();
    processHandler.Stop();
}
“MainServiceThread”创建事件,再次启动一个新线程,然后等待事件

protected override void OnStop()
{
    if (serviceThread != null)
    {
        serviceThread.StopThread(); // Event is signalled in there
        serviceThread.WaitForThreadToReturn(); // This calls thread.Join() on the MainServiceThread thread
    }
    base.OnStop();
}
private void StartHandlerAndWaitForServiceStop()
{
    processHandler.Start(serviceStopEvent);
    serviceStopEvent.Wait();
    processHandler.Stop();
}
processHandler线程订阅此WMI查询:

watcher = new ManagementEventWatcher(new ManagementScope("root\\CIMV2"),
    new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
watcher.EventArrived += HandleNewProcessCreated;
如果对新进程名称感兴趣,我将创建一个新的“throttler”线程,该线程实际上只是在循环上暂停进程、休眠、恢复进程,然后再次休眠:

while (true)
{
    ntresult = Ntdll.NtResumeProcess(processHandle);
    if (ntresult != Ntdll.NTSTATUS.STATUS_SUCCESS)
    {
        if (ntresult != Ntdll.NTSTATUS.STATUS_PROCESS_IS_TERMINATING)
            LogSuspendResumeFailure("resume", ntresult);
        break;
    }
    Thread.Sleep(resumeTime);

    ntresult = Ntdll.NtSuspendProcess(processHandle);
    if (ntresult != Ntdll.NTSTATUS.STATUS_SUCCESS)
    {
        if (ntresult != Ntdll.NTSTATUS.STATUS_PROCESS_IS_TERMINATING)
            LogSuspendResumeFailure("suspend", ntresult);
        break;
    }
    Thread.Sleep(suspendTime);

    if (++loop >= loopsBeforeCheckingStopEvent)
    {
        if (stopEvent.IsSet) break;
        loop = 0;
    }
}
如果服务接收到停止命令,它将设置ManualResetEventSlim事件。任何“节流”进程的线程都将在1秒内看到它,并中断循环/返回。进程处理程序线程将等待所有这些线程返回,然后再返回。此时,上面发布的StartHandlerAndWaitForServiceStop()方法将返回,其他一直在这里和那里等待的线程将返回

绝大多数时候,我停止了服务,它停止没有任何问题。这与我运行的节流线程是0还是500无关,也与服务运行时是否创建过节流线程无关

但是,当我尝试停止它时(通过services.msc),它会挂起。昨天,当进程处于这种状态时,我成功地创建了一个完整的转储。我用Process Explorer创建了转储

转储文件显示许多my线程已挂起:

0:010> ~
   0  Id: 1840.c34 Suspend: 0 Teb: 000007ff`fffdd000 Unfrozen
   1  Id: 1840.548 Suspend: 0 Teb: 000007ff`fffdb000 Unfrozen
   2  Id: 1840.9c0 Suspend: 0 Teb: 000007ff`fffd9000 Unfrozen
   3  Id: 1840.1da8 Suspend: 0 Teb: 000007ff`fffd7000 Unfrozen
   4  Id: 1840.b08 Suspend: 3 Teb: 000007ff`fffd5000 Unfrozen
   5  Id: 1840.1b5c Suspend: 0 Teb: 000007ff`ffef6000 Unfrozen
   6  Id: 1840.af0 Suspend: 2 Teb: 000007ff`ffef2000 Unfrozen
   7  Id: 1840.c60 Suspend: 0 Teb: 000007ff`ffef0000 Unfrozen
   8  Id: 1840.1d94 Suspend: 4 Teb: 000007ff`ffeee000 Unfrozen
   9  Id: 1840.1cd8 Suspend: 4 Teb: 000007ff`ffeec000 Unfrozen
. 10  Id: 1840.1c64 Suspend: 0 Teb: 000007ff`ffefa000 Unfrozen
  11  Id: 1840.1dc8 Suspend: 0 Teb: 000007ff`fffd3000 Unfrozen
  12  Id: 1840.8f4 Suspend: 0 Teb: 000007ff`ffefe000 Unfrozen
这与我在Process Explorer中看到的情况有关——在我“节流”的两个进程中,一个永久挂起,另一个永久恢复。因此,这些节流线程被有效地挂起,因为它们不再执行它们的工作。它们不被挂起就不可能停止,因为我已经对其进行了错误处理,任何异常都会导致这些线程记录信息并返回。另外,他们的调用堆栈没有显示错误。由于某些错误,他们没有永久睡眠,因为两次睡眠的睡眠时间分别为22毫秒和78毫秒,在我尝试停止服务之前,它工作正常

public static void Main(string[] args)
{
    ...
    else if (Args.RunService)
    {
        Logger.Options.LogToFile = true;
        MSPO.Logging.Logger.Start();
        RunService();
        MSPO.Logging.Logger.Stop();
    }
    ...
}

private static void RunService()
{
    service = new ProcessThrottlerService();
    System.ServiceProcess.ServiceBase.Run(service);
}
所以我试图理解这些线程是如何被挂起的。我唯一的怀疑是GC,因为它在回收/压缩内存时挂起了线程

我已经粘贴了的内容!eestack和~*kb在此处:

我应该提到,我没有符号,因为在创建转储时,我已经多次重建了应用程序。然而,由于它是.NET,我想这不是一个问题

从eestack来看,我认为这些是“我的”线索:

  • 线程0:主服务线程,它仍在ServiceBase.Run方法中
  • 线程4:这是我的记录器线程。该线程将花费其大部分生命等待阻塞队列
  • 线程6:我的MainServiceThread线程,它正在等待设置事件
  • 线程8和9:都是“throttler”线程,执行我在上面发布的循环
  • 线程10:该线程似乎正在执行OnStop()方法,处理service stop命令也是如此
就是这样,根据转储文件,线程4、6、8和9被挂起。因此,除了主线程和处理OnStop()方法的线程外,所有“我的”线程都被挂起

现在我对GC和调试.NET的东西不太了解,但是线程10在我看来是不可靠的。摘自调用堆栈:

Thread  10
Current frame: ntdll!NtWaitForMultipleObjects+0xa
Child-SP         RetAddr          Caller, Callee
000000001a83d670 000007fefdd41420 KERNELBASE!WaitForMultipleObjectsEx+0xe8, calling ntdll!NtWaitForMultipleObjects
000000001a83d6a0 000007fef4dc3d7c clr!CExecutionEngine::ClrVirtualAlloc+0x3c, calling kernel32!VirtualAllocStub
000000001a83d700 000007fefdd419bc KERNELBASE!WaitForMultipleObjectsEx+0x224, calling ntdll!RtlActivateActivationContextUnsafeFast
000000001a83d710 000007fef4e9d3aa clr!WKS::gc_heap::grow_heap_segment+0xca, calling clr!StressLog::LogOn
000000001a83d730 000007fef4e9cc98 clr!WKS::gc_heap::adjust_limit_clr+0xec, calling clr!memset
000000001a83d740 000007fef4df398d clr!COMNumber::FormatInt32+0x8d, calling clr!LazyMachStateCaptureState
000000001a83d750 000007fef4df398d clr!COMNumber::FormatInt32+0x8d, calling clr!LazyMachStateCaptureState
000000001a83d770 00000000778a16d3 kernel32!WaitForMultipleObjectsExImplementation+0xb3, calling kernel32!WaitForMultipleObjectsEx
000000001a83d7d0 000007fef4e9ce73 clr!WKS::gc_heap::allocate_small+0x158, calling clr!WKS::gc_heap::a_fit_segment_end_p
000000001a83d800 000007fef4f8f8e1 clr!WaitForMultipleObjectsEx_SO_TOLERANT+0x91, calling kernel32!WaitForMultipleObjectsExImplementation
000000001a83d830 000007fef4dfb798 clr!Thread::GetApartment+0x34, calling clr!GetThread
000000001a83d860 000007fef4f8f6ed clr!Thread::GetFinalApartment+0x1a, calling clr!Thread::GetApartment
000000001a83d890 000007fef4f8f6ba clr!Thread::DoAppropriateAptStateWait+0x56, calling clr!WaitForMultipleObjectsEx_SO_TOLERANT
000000001a83d8d0 000007fef4f8f545 clr!Thread::DoAppropriateWaitWorker+0x1b1, calling clr!Thread::DoAppropriateAptStateWait
000000001a83d990 000007fef4ecf167 clr!ObjectNative::Pulse+0x147, calling clr!HelperMethodFrameRestoreState
000000001a83d9d0 000007fef4f8f63b clr!Thread::DoAppropriateWait+0x73, calling clr!Thread::DoAppropriateWaitWorker
000000001a83da50 000007fef4f0ff6a clr!Thread::JoinEx+0xa6, calling clr!Thread::DoAppropriateWait
000000001a83dac0 000007fef4defd90 clr!GCHolderBase<0,0,0,0>::EnterInternal+0x3c, calling clr!Thread::EnablePreemptiveGC
000000001a83daf0 000007fef4f1039a clr!ThreadNative::DoJoin+0xd8, calling clr!Thread::JoinEx
000000001a83db20 000007fef45f86f3 (MethodDesc 000007fef3cbe8d8 +0x1a3 System.Threading.SemaphoreSlim.Release(Int32)), calling 000007fef4dc31b0 (stub for System.Threading.Monitor.Exit(System.Object))
000000001a83db60 000007fef4dfb2a6 clr!FrameWithCookie<HelperMethodFrame_1OBJ>::FrameWithCookie<HelperMethodFrame_1OBJ>+0x36, calling clr!GetThread
000000001a83db90 000007fef4f1024d clr!ThreadNative::Join+0xfd, calling clr!ThreadNative::DoJoin
000000001a83dc40 000007ff001723f5 (MethodDesc 000007ff001612c0 +0x85 MSPO.Logging.MessageQueue.EnqueueMessage(System.String)), calling (MethodDesc 000007fef30fde88 +0 System.Collections.Concurrent.BlockingCollection`1[[System.__Canon, mscorlib]].TryAddWithNoTimeValidation(System.__Canon, Int32, System.Threading.CancellationToken))
000000001a83dcf0 000007ff001720e9 (MethodDesc 000007ff00044bb0 +0xc9 ProcessThrottler.Logging.Logger.Log(LogLevel, System.String)), calling (MethodDesc 000007ff00161178 +0 MSPO.Logging.MessageFormatter.QueueFormattedOutput(System.String, System.String))
000000001a83dd10 000007fef4f101aa clr!ThreadNative::Join+0x5a, calling clr!LazyMachStateCaptureState
000000001a83dd30 000007ff0018000b (MethodDesc 000007ff00163e10 +0x3b ProcessThrottler.Service.MainServiceThread.WaitForThreadToReturn()), calling 000007fef4f10150 (stub for System.Threading.Thread.JoinInternal())
000000001a83dd60 000007ff0017ff44 (MethodDesc 000007ff00049f30 +0xc4 ProcessThrottler.Service.ProcessThrottlerService.OnStop()), calling 000007ff0004d278 (stub for ProcessThrottler.Service.MainServiceThread.WaitForThreadToReturn())
000000001a83dda0 000007fef63fcefb (MethodDesc 000007fef63d65e0 +0xbb System.ServiceProcess.ServiceBase.DeferredStop())

我已经发现了原因。这与我的代码无关。这是Process Explorer中的一个错误

我的程序是写在target.NET4.0上的。如果我使用Process Explorer查看任何线程的调用堆栈,Process Explorer将挂起该线程,而不会恢复该线程。它应该做的是在获取调用堆栈时挂起线程,然后立即恢复它。但它并没有恢复线程,也不是我的托管线程

我可以用以下非常简单的代码复制它:

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                Console.WriteLine(i.ToString());
            }
        }   
    }
}
使用系统;
名称空间测试
{
班级计划
{
静态void Main(字符串[]参数)
{
对于(int i=0;i
如果我将其编译为目标.NET4.0或更高版本,运行它,并使用Process Explorer打开运行循环的线程,则该线程将被挂起。恢复按钮将变为可用,我可以单击它来恢复线程。多次打开线程会导致线程多次挂起;我通过使用Windbg查看线程的挂起计数来确认这一点


如果我将其编译为4.0以下的目标版本(尝试了2.0和3.5),我在Process Explorer中打开的线程不会保持挂起状态。

您正在攻击NtSuspendProcess(),并发现您的整个服务挂起很奇怪。是不是只有我看到了共同事件?我的水晶球说,您在OpenProcess()上的错误检查不够充分,最终将得到一个空进程句柄。哈拉基里,你会暂停自己:)@HansPassant哈哈,是的,但我相信这一切都是巧合。我很漂亮