我在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