c# - 为什么后台垃圾收集有时会暂停我的应用程序,我该如何防止它发生?

标签 c# .net wpf garbage-collection

我们有一个大型(=一天中可能需要 200 - 500 MB 或更多内存)WPF 应用程序,一天要使用多个小时。有时,应用程序会无缘无故地挂起。分析进程转储显示垃圾收集处于事件状态以及暂停应用程序的原因。

我们正在使用 .NET 4.0,据我了解,新引入的“background garbage collection”应该会减少整个进程因垃圾收集(前台垃圾收集)而被阻塞的时间。

但是,即使暂停发生的频率不高,如果暂停时间超过几秒(就是这种情况),也会中断工作流程。

这引出了以下问题:

  • 我读到在服务器环境中,默认情况下不使用后台垃圾回收。我们的应用程序确实在服务器操作系统(Windows Server 2003 R2 x64)上运行,尽管它是客户端应用程序(而不是服务器应用程序)。这是否意味着不使用后台垃圾回收?还是仅适用于服务/ASP.NET?

  • 假设确实启用了后台垃圾回收,我如何才能防止前台垃圾回收过于频繁/耗时过长?我目前的方法是检测应用程序中的空闲时间(例如,应用程序未使用 5 分钟或更长时间)并启动强制垃圾收集,以便在稍后使用应用程序时不会发生这种情况.

调试诊断进程转储信息: enter image description here

堆栈跟踪:

mscorlib_ni!System.GC.Collect(Int32, System.GCCollectionMode)+47 
[[InlinedCallFrame] (System.GC._Collect)] System.GC._Collect(Int32, Int32) 
PresentationCore_ni!MS.Internal.MemoryPressure.ProcessAdd()+1d0 
PresentationCore_ni!MS.Internal.MemoryPressure.Add(Int64)+39 
PresentationCore_ni!System.Windows.Media.SafeMILHandleMemoryPressure..ctor(Int64)+43 
PresentationCore_ni!System.Windows.Media.SafeMILHandle.UpdateEstimatedSize(Int64)+38 
PresentationCore_ni!System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()+df 
PresentationCore_ni!System.Windows.Media.Imaging.RenderTargetBitmap..ctor(Int32, Int32, Double, Double, System.Windows.Media.PixelFormat)+d9 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetRenderTargetBitmapForVisual(Int32, Int32, System.Windows.Media.Visual)+b1 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForFrameworkElement(System.Windows.FrameworkElement)+89 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForTransparentWindowsFormsHost(System.Windows.Forms.Integration.WindowsFormsHost)+4b 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForWindowsFormsHost(System.Windows.Forms.Integration.WindowsFormsHost, System.Windows.Media.Brush)+1f 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.WindowsFormsHostPropertyMap.BackgroundPropertyTranslator(System.Object, System.String, System.Object)+109 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.PropertyMap.RunTranslator(System.Windows.Forms.Integration.PropertyTranslator, System.Object, System.String, System.Object)+32 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.WindowsFormsHost.ArrangeOverride(System.Windows.Size)+277 
PresentationFramework_ni!System.Windows.FrameworkElement.ArrangeCore(System.Windows.Rect)+8e3 
PresentationCore_ni!System.Windows.UIElement.Arrange(System.Windows.Rect)+385 
PresentationCore_ni!System.Windows.ContextLayoutManager.UpdateLayout()+2b5 
PresentationCore_ni!System.Windows.ContextLayoutManager.UpdateLayoutCallback(System.Object)+19 
PresentationCore_ni!System.Windows.Media.MediaContext+InvokeOnRenderCallback.DoWork()+10 
PresentationCore_ni!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()+76 
PresentationCore_ni!System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)+8a 
PresentationCore_ni!System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(System.Object)+6e 
WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+53 
WindowsBase_ni!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+42 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.InvokeImpl()+8d 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)+38 
mscorlib_ni!System.Threading.ExecutionContext.runTryCode(System.Object)+51 
[[HelperMethodFrame_PROTECTOBJ] (System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup)] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object) 
mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+6a 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+7e 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+2c 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.Invoke()+68 
WindowsBase_ni!System.Windows.Threading.Dispatcher.ProcessQueue()+15e 
WindowsBase_ni!System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+63 
WindowsBase_ni!MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+be 
WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)+7d 
WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+53 
WindowsBase_ni!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+42 
WindowsBase_ni!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)+b4 
WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)+104 
WindowsBase_ni!DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef)+3c 
[[InlinedCallFrame]] 
WindowsBase_ni!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)+c1 
WindowsBase_ni!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)+49 
PresentationFramework_ni!System.Windows.Application.RunDispatcher(System.Object)+5b 
PresentationFramework_ni!System.Windows.Application.RunInternal(System.Windows.Window)+74 
PresentationFramework_ni!System.Windows.Application.Run(System.Windows.Window)+2b 

最佳答案

您可以使用 app.config 更改 GC 模式。默认情况下,服务器 Windows 已禁用后台 GC。原因很简单 - 后台 GC 意味着更短的可见延迟(最适合用户应用程序),而前台 GC 具有更好的总吞吐量。

但是,如果 GC 收集时间长到足以产生明显的挂起,则您可能做错了什么。这与直接的内存量无关,更有可能是此类内存处理效率低下的结果。到目前为止,我见过的最严重的违规行为是手动使用 GC.Collect - 这基本上会破坏分代 GC(以及其他优化)的所有性能提升,这是非常重要的。 Debug Diag 片段似乎表明事实确实如此——您似乎是手动启动收集;但我从未使用过该工具,因此它可能是误报。

200-500 MiB 当然不算多。关键是收集的难易程度。这取决于对象如何分配到不同的世代、有多少对象(而不是它们的总大小,尽管这显然也起作用)、内存位置和许多其他因素。

有点违反直觉,在 .NET 中,强制自己重用对象并像在 C++ 中一样进行类似的优化通常不是一个好主意。最有可能的是,它会导致更差的 GC 性能,因为它会损害内存局部性和堆的世代分区。

重点还是——profile。附加并发可视化工具和 CLRProfiler。它们会告诉您 GC 实际做了多少工作,并可以帮助您理解原因。

并重申 - 不要使用 GC.Collect。我从未见过它会导致性能提升,而且我已经多次看到它会破坏 GC 性能。我见过的唯一合理的用例是在基准测试中——实际上足够简单,可以从强制收集中获益。如果您不尝试这些微优化,堆实际上几乎可以和堆栈一样快(通过主要处理范围受限的对象 - 一个方便的快捷方式)。 .NET的GC其实很好用。

编辑:

根据附加信息,我担心在 WPF 应用程序中执行大量后台工作时,这实际上可能是一个重大问题。如果您将后台工作分离到一个额外的进程中,并且只威胁 WPF 应用程序作为某些底层服务的 GUI 前端,您可能会解决整个问题。显然,这不会很容易实现......

另一种选择是尝试尽可能地限制 WPF 垃圾 - WPF 使用的数据越少,调用其 GC.Collect(2) 的倾向就越小。

强制 GC 像在非服务器窗口上一样运行的 app.config 设置非常简单:

<configuration>
 <runtime>
  <gcServer enabled="false" />
  <gcConcurrent enabled="true" />
 </runtime>
</configuration>

它应该有助于减少 UI 无响应的时间 - 前提是您有多个可用的处理器内核。

关于c# - 为什么后台垃圾收集有时会暂停我的应用程序,我该如何防止它发生?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25158665/

相关文章:

c# - 使用NEST和C#进行 Elasticsearch 滚动

c# - Server.MapPath 不返回映射到 Web-App 子文件夹中的虚拟目录的正确物理路径

c# - 将新项目添加到列表中替换列表中的旧项目

c# - 在 C# 中通过 "dynamic overload"进行单/双分派(dispatch)

c# - 在循环 wpf 中执行下一个代码之前等待

c# - 可以将代码从 Web 表单传递到 View 吗? (C#)

.net - 无法从Docker托管的Web应用程序调用外部api

WPF:沿路径查找元素

c# - 使用 WPF-MVVM 在运行时展开所有 WPF TreeView 节点

c# - 从立即窗口执行方法