首页 文章

停止时服务偶尔挂起:悬挂线程

提问于
浏览
1

我在C#中编写了一个针对.NET 4.0的Windows服务,当我尝试停止服务时,奇怪的情况下它将完全挂起 . 我注意到从转储文件中看到我的一些线程被挂起了,虽然我没有在我的代码中暂停它们 .

环境是Windows Server 2008R2 64位,虽然我在Windows 7 64bit上观察到相同的挂起 . .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();
}

“MainServiceThread”创建事件,再次启动新线程,然后等待事件 .

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创建了转储 .

转储文件显示我的一些线程被挂起:

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毫秒,并且在我试图停止服务之前它工作正常 .

所以我试图了解这些线程如何被暂停 . 我唯一的怀疑是GC,因为它在回收/压缩内存时暂停线程 .

我在这里粘贴了!eestack和〜* kb的内容:http://pastebin.com/rfQK0Ak8

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

从eestack,这些是我认为是“我的”线程:

  • 线程0:主服务线程,它仍然在ServiceBase.Run方法中 .

  • 线程4:那是我的 Logger 线程 . 该线程将花费大部分时间等待阻塞队列 .

  • 线程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())

我可以发布更多的代码来显示我的每个函数正在做什么,但是我真的没有看到上面的调用堆栈并且看到它's doing some GC stuff after I tell it to log a string to a queue. But none of that GC stuff looks dodgy, at least not compared to what I'我看到http://blogs.msdn.com/b/tess/archive/2008/02/11/hang-caused-by-gc-xml-deadlock.aspx我有一个配置文件告诉我它使用gcServer,但我'm almost certain it'不使用该设置,因为在我之前的测试中 GCSettings.IsServerGC 总是返回false .

那么......有没有人对我的线程被暂停的原因有任何建议?

这是我的OpenProcess方法BTW,它获取暂停/恢复进程的句柄,以回应Hans的评论:

private void GetProcessHandle(CurrentProcessDetails process)
{
    IntPtr handle = Kernel32.OpenProcess(
        process.Settings.RequiredProcessAccessRights,
        false,
        (uint)process.ID
        );
    if (handle == IntPtr.Zero)
        throw new Win32ExceptionWrapper(
            string.Format("Failed to open process {0} {1}", 
            process.Settings.ProcessNameWithExt, process.IDString));
    process.Handle = handle;
}

1 回答

  • 1

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

    我的程序是针对.NET 4.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());
                }
            }   
        }
    }
    

    如果我将其编译为目标.NET 4.0或更高版本,运行它,并使用Process Explorer打开运行循环的线程,该线程将被挂起 . 恢复按钮将可用,我可以单击它以恢复该线程 . 多次打开线程会导致多次暂停;我通过使用Windbg查看线程的挂起计数来确认这一点 .

    如果我将它编译为低于4.0的目标版本(尝试过2.0和3.5),我在Process Explorer中打开的线程不会保持挂起状态 .

相关问题