我在C#中编写了一个Windows服务,目标是.NET 4.0,当我尝试停止服务时,它将在奇怪的时候完全挂起。我注意到从一个转储文件中看到一些线程被挂起,尽管我并没有在我的代码中自行挂起它们。服务在停止时偶尔挂起:挂起的线程
环境是Windows Server 2008R2 64bit,尽管我在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;
如果新的进程名很感兴趣,我创建了一个新的“节流”的线程,有效地只是暂停过程中,睡觉,简历这个过程中,又一次睡上一个循环:
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个throttler线程在运行无关,而且无论服务运行时是否创建过任何线程。
然而,当我试图阻止它(通过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:这是我的记录器线程。该线程将花费其大部分时间在阻塞队列中等待。
- 线程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())
我可以发布更多的代码显示了每个在我的职务是干什么的,但我真的不认为这是我的代码死锁,因为线程不会成为悬浮在案件。所以我正在查看上面的调用堆栈,并在我告诉它将一个字符串记录到一个队列后看到它正在执行一些GC的东西。但是没有一个GC的东西看起来不可靠,至少没有与我在http://blogs.msdn.com/b/tess/archive/2008/02/11/hang-caused-by-gc-xml-deadlock.aspx中看到的相比较。我有一个配置文件来告诉它使用gcServer,但我几乎可以肯定它没有使用该设置,因为在我之前的测试中GCSettings.IsServerGC
总是返回错误。
所以......有没有人有任何建议,为什么我的线程被暂停?
这是我的OpenProcess方法BTW它获取的句柄进程暂停/恢复,响应汉斯的评论:
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;
}
你是黑客NtSuspendProcess()和觉得奇怪,你的整个业务挂起。这只是我看到的共同点?我的水晶球说你在OpenProcess()上的错误检查是不够的,你最终会得到一个NULL处理句柄。切腹,你会暂停自己:) –
@HansPassant LOL是,但我敢肯定,这一切都巧合。我很确定我正确使用OpenProcess(),并将结果与IntPtr.Zero进行比较。另外,如果失败则有件事情我不暂停/恢复它会失败,就像从GetModuleFileNameEx获得的文件名比较我所期望的,等 – eurotrash