. Net插件导致高内存消耗/泄漏(不在托管堆中)
我有一个非常大而复杂的AutoCAD.net插件。我试图避免详细解释这个插件的工作原理,因为我的问题是一个一般性的问题。如果我没有正确使用术语 AppDomain 和 Managed Heap,我深表歉意。我对 .net 中的内存分配系统以及如何实现 autocad 中的 .net 插件系统缺乏详细的了解,因此我已尽力而为。该插件利用一组复杂的动态块。这些动态块的属性是以编程方式操作的。插件订阅 Document.ImplicitSelectionChanged,以便检测何时选择了插件创建的一个或多个动态块。我还订阅了每个BlockReference的ObjectClosed事件,以检测何时修改了块。当为相关块触发 ImplicitSelectionChanged 事件时,插件将读取所选块属性的值。也可以设置属性。从本质上讲,我公开了一个自定义块属性编辑器,以便可以在给定其他因素的情况下对哪些块属性可用来强制执行复杂的工程规则。此外,这些规则可能规定,如果将块属性设置为某个值,则其他属性值也会自动更改。因此,大多数块属性对用户是隐藏的,只能通过我的属性编辑器控件进行编辑。
我遇到的问题是,当重复选择其中一些块时,AutoCAD会分配大量内存。此内存永远不会被释放,导致 acad.exe 消耗 GB 的内存。即使一遍又一遍地选择/取消选择同一组块,也会发生这种情况。
我已经使用内存探查器(JustTrace)分析了我的插件在其中运行的AppDomain的托管堆。此 AppDomain 中不存在内存泄漏。Acad.exe 可能使用 4GB 内存,而托管堆仅使用几百 MB。比较执行有问题的操作之前和之后的堆快照并不能揭示此内存消耗的来源。
我煞费苦心地包装了所有在使用块中实现IDisposable的ACAD API类实例,或者以确保在主UI线程上显式调用Dispose,以消除由GC调用dispose在另一个线程上引起的访问冲突,所以我不相信这是问题的根源。当我强制收集时,GC应该调用Dis dispose,我已经尝试过了。
我正在寻求有关查找这种内存消耗原因的一般见解,这似乎发生在AutoCAD的本机代码中。保存绘图并关闭/打开它会导致内存被解除分配。似乎我已经导致了内存泄漏(嗯,很多内存泄漏),但我不知道如何追踪源头。任何建议将不胜感激。
请注意,我已尝试禁用 UNDO,但内存消耗没有明显的变化。
谢谢,
凯文
**** Hidden Message ***** 为了打开ObjectId ForRead,您可能正在使用数据库事务...您提到使用块,但您是否曾经提交()事务?
此外,我相信您的应用程序对您来说很有意义,但听起来您在SseltionChanged读取对象属性的逻辑为时过早(除非动态更新了一些无模式对话框?)...选择是一个主要功能,因此除非您随后对单击事件(例如ContextMenu)或命令进行操作,否则您将在您的应用程序IMO上花费大量精力。
需要更多信息(和代码)。
干杯
抱歉-生产非常繁忙的一天(客户决定重新配置整个1,000个AC开发,Grr),所以我选择了您的一些响应以方便-
我建议您正确地释放您打开的对象,最容易通过Commit()-在用于打开所述对象的Transaction中完成(是的,甚至OpenMode.ForRead)。打开ForRead在内存、性能等方面的成本较低,但正如您所注意到的那样,它仍然会消耗;它不是免费的。
您当然可以在没有事务的情况下完成所有这些;这只是您的更多工作,当您忽略实现减轻所有负面潜力所需的代码逻辑时,这是灾难性的。事务,在性能等方面成本更高。,但更易于编码和维护——特别是对于那些刚接触AutoCAD的人。NET API,恕我直言。事实上,你擅长。NET_before_潜入AutoCAD。NET API是一件好事,大多数人从低级开始(像我一样),成为一个CAD Monkey,学习脚本、LISP等,然后尝试升级到。NET API。
无论如何,在您的事务上使用块并调用Commit()*应该*清理您的内存消耗问题...*IF*这是唯一的问题;没有代码可看,无法知道,测试等。
为了帮助限定我的建议,即您应该在使用事务时调用Commit(),这里是来自Kean的片段:
干杯 谢谢我正在努力确保现在总是调用Commit。我将发布一篇关于此更改如何影响内存使用的更新。 当使用 AutoCAD API 9 时,调用 dispose 不会释放任何内存,它会在内存中对象上调用 close。
可能是选择集未被释放,或者有其他事情,但是给定的信息,我所知道的只是通过删除订阅选择集更改事件的代码来缩小原因范围,并查看是否仍然发生,然后删除订阅关闭事件的代码等......以查看哪个区域导致问题,然后删除导致问题的较小部分区域,直到找到为止。 对所有事务调用 Commit,即使那些只读取数据的事务,也不会对内存消耗或性能产生明显的影响。 Jeff H,
选择集事件驱动有问题的代码。所以,是的,这将消除内存泄漏。
关闭偶数处理程序不是内存泄漏的原因。我尝试按照您的建议禁用系统的这一部分,并且没有更改或对内存消耗只有轻微的更改。
我相信 AutoDesk 论坛成员是正确的,因为内存消耗是由我的应用程序中使用的动态块的复杂性和我的代码对这些块的属性的操纵共同引起的。对我来说,AutoCAD从不释放用于这些复杂操作的内存是没有意义的。从字面上看,我可以通过一遍又一遍地选择/取消截取同一组块来使AutoCAD使用12GB的内存。每次选择块并调用我的代码时,都会分配更多内存。新分配的大部分内存永远不会释放。
我正在考虑创建一个测试插件,它只包含我的代码库的一小部分,但可以用来复制这个内存泄漏。这将花费我几个小时,但通过这种方式,我可以在这里提供插件,并将其发送到ADN并要求他们接受。
凯文,这将是我处理这个问题的方式。 我准备了一个可运行的插件,可用于重现我所描述的内存泄漏。附加的解决方案是在VS 2010中编写的,并使用AutoCAD 2014和2016进行了测试(我只在2014年进行了调试)。它应该在2013年至2016年间的任何版本中运行。我在bin\Debug中包含了一个工作dll。
我已经包含了我的插件使用的动态块的一小部分,以便重现问题。插件定义了一个命令(RunAutomatedMemoryLeakTest)。插件为每个动态块创建10个实例,然后将每个可用的属性值设置3次,其中属性类型代码为5。内存量与每个属性设置的块数/次数成正比。通过更改for循环可以轻松调整内存消耗量。
插入块和读取块属性似乎都不会导致内存消耗。内存消耗是由调用DynamicBlockHelper.SetParameter.引起的
这是命令的代码(对不起,我的雇主需要vb.net):
_
Public Shared Sub AutoMemoryLeakTestCommand()
If Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument IsNot Nothing Then
Dim blockObjectIDs As New List(Of ObjectId)
Dim directoryPath As String = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
For x As Integer = 1 To 10
For Each filePath In Directory.GetFiles(directoryPath)
If Path.GetExtension(filePath).Equals(".dwg", StringComparison.OrdinalIgnoreCase) Then
DynamicBlockHelper.AddDynBlockToDrawingFromFile(filePath, Path.GetFileNameWithoutExtension(filePath))
blockObjectIDs.Add(DynamicBlockHelper.InsertBlock(HostApplicationServices.WorkingDatabase, BlockTableRecord.ModelSpace, New Point3d(0, 0, 0), Path.GetFileNameWithoutExtension(filePath), 1, 1, 1, "NONE"))
End If
Next
Next
For x As Integer = 1 To 3
For Each objID In blockObjectIDs
Dim blockProperties = DynamicBlockHelper.GetBlockProperties(objID)
For Each blockProp In blockProperties
Dim propertyAttribs() As String = blockProp.Key.Split("~")
Dim propertyName As String = propertyAttribs(0)
Dim propertyType As String = propertyAttribs(2)
If propertyType = 5 AndAlso blockProp.Value.Count > 1 Then
For Each propValue As String In blockProp.Value
DynamicBlockHelper.SetParameter(objID, propertyName, propValue)
Next
End If
Next
Next
Next
End If
End Sub
这是SetParameter方法的代码:
Public Shared Function SetParameter(ByVal BlockID As ObjectId, ByVal ParameterName As String, ByVal Value As String) As Boolean
Dim doc As Document = ApplicationServices.Application.DocumentManager.GetDocument(BlockID.Database)
Using lock As DocumentLock = doc.LockDocument()
Using myTrans As Transaction = BlockID.Database.TransactionManager.StartOpenCloseTransaction()
Try
Using myBRef As BlockReference = myTrans.GetObject(BlockID, OpenMode.ForWrite)
For Each myDynamProp As DynamicBlockReferenceProperty In _
myBRef.DynamicBlockReferencePropertyCollection
If myDynamProp.PropertyName.Equals( _
ParameterName, StringComparison.OrdinalIgnoreCase) = True Then
myDynamProp.Value = Value
myTrans.Commit()
Return True
End If
Next
End Using
Catch ex As System.Exception
myTrans.Abort()
Try
EventLog.WriteEntry("MemLeakTest", ex.GetType().Name + ": " + ex.Message + Environment.NewLine + ex.StackTrace, EventLogEntryType.Error)
Catch eex As System.Exception
End Try
End Try
Return False
End Using
End Using
End Function
谢谢,
凯文 我收到了ADN的回复,说他们可以重现这个问题(消息如下)。这是我的第一个“更改请求”。有人有关于如何快速修复的建议吗?
页:
[1]
2