I have a routine that runs a cycle through 36 large dynamic blocks, then finishes. Somewhere in this code I'm either leaking memory, running a never ending loop, or AutoCAD gets stuck on something. I have a 12gb machine, and this file only uses a few MB on the hard drive. So even after I save and close the file, AutoCAD is using 100% of 1 core CPU (4 core i7) in 0 drawing state stuck at 11gb of Memory (Private Working Set). So, What is this program doing in 0 drawing state, and how is my code responsible? Any advice (good or bad) would be appreciated, I'm looking for clues to clean this up. I can't even close AutoCAD after running this routine, I have to kill it with task manager.
<CommandMethod("SetCurveOffset")> _ Public Shared Sub SetCurveOffset() Try Dim doc As Document = AApplication.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using lock As DocumentLock = doc.LockDocument() Using trans As Transaction = db.TransactionManager.StartTransaction Dim bt As BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead) Dim btrModelSpace As BlockTableRecord = trans.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite) Dim curves As New List(Of ClearanceCurve) 'select curves in drawing Dim psr As Autodesk.AutoCAD.EditorInput.PromptSelectionResult Dim pso As New Autodesk.AutoCAD.EditorInput.PromptSelectionOptions pso.MessageForAdding = "Select Curve-dyn blocks" pso.MessageForRemoval = "Remove Curve-dyn blocks" pso.AllowDuplicates = False pso.SingleOnly = False psr = doc.Editor.GetSelection(pso) If psr.Status = Autodesk.AutoCAD.EditorInput.PromptStatus.OK Then Dim pm As New ProgressMeter pm.SetLimit(psr.Value.Count) pm.Start("Processing Selection") For Each so As Autodesk.AutoCAD.EditorInput.SelectedObject In psr.Value Dim ent As Entity = trans.GetObject(so.ObjectId, OpenMode.ForRead) If TypeOf ent Is BlockReference Then Dim br As BlockReference = ent If br.IsDynamicBlock Then Dim btrDynBlock As BlockTableRecord = trans.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead) If btrDynBlock.Name.Contains(CurveDynBlockName) Then Dim curve As New ClearanceCurve(br) curves.Add(curve) End If End If br = Nothing End If pm.MeterProgress() ent = Nothing Next 'each curve is a dynamic block with the cars and loads already attached. 'the only thing for this program to do is to set the max clearance offset value. pm.Stop() pm.SetLimit(curves.Count) pm.Start("Calculating Curves") For Each Curve As ClearanceCurve In curves Curve.CalculateClearance(trans) pm.MeterProgress() Next pm.Stop() 'that should do it. trans.Commit() pm = Nothing End If bt = Nothing btrModelSpace = Nothing curves = Nothing psr = Nothing pso = Nothing End Using End Using doc = Nothing db = Nothing System.GC.Collect() Catch ex As System.Exception MsgBox(ex.ToString) End Try End Sub
Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.ApplicationServices Imports ACApp = Autodesk.AutoCAD.ApplicationServices.Application Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.Geometry Public Class ClearanceCurve #Region "Properties" Property CurveBlock As BlockReference = Nothing Property CenterCar As ClearanceCar = Nothing Property LeftCar As ClearanceCar = Nothing Property RightCar As ClearanceCar = Nothing Property Loads As New List(Of ClearanceLoad) Property MaxClearanceOffset As Double Property TrackHalfWidth As Double Property Radius As Double Property Degree As Double Private maxPoint As Point3d Private minPoint As Point3d Private maxDist As Double = 0 Private radCenter As Point3d Private pm As ProgressMeter #End Region #Region "Load/Save" Public Sub New(ByRef curveBlockRef As BlockReference) CurveBlock = curveBlockRef 'dissect this block into its 3 cars, if found, and all other blocks within it 'any other block is to be considered a load ProcessCurveBlock() End Sub Public Sub New() 'this is where we intend to have the block and/or ref created by code alone. End Sub #End Region #Region "Methods" Private Sub ProcessCurveBlock() Dim doc As Document = ACApp.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Using trans As Transaction = db.TransactionManager.StartTransaction Dim btr As BlockTableRecord = trans.GetObject(CurveBlock.BlockTableRecord, OpenMode.ForRead) Dim mBlockRef As Matrix3d = CurveBlock.BlockTransform For Each oid As ObjectId In btr Dim ent As Entity = trans.GetObject(oid, OpenMode.ForRead) If TypeOf (ent) Is BlockReference Then Dim br As BlockReference = ent Select Case br.Name Case "Car1" 'center car CenterCar = New ClearanceCar(br) Case "Car2" 'right car RightCar = New ClearanceCar(br) Case "Car3" 'left car LeftCar = New ClearanceCar(br) Case Else 'special load Loads.Add(New ClearanceLoad(br)) End Select ElseIf TypeOf (ent) Is Arc Then Dim arc As Arc = ent If arc.ColorIndex = 5 Then radCenter = arc.Center End If End If Next Radius = GetDynamicPropertyValue(CurveBlock, CenterRadiusDynPropertyName) Degree = GetDynamicPropertyValue(CurveBlock, DegreeDynPropertyName) TrackHalfWidth = CDbl(GetDynamicPropertyValue(CurveBlock, PlateCClearanceDynPropertyName)) / 2 End Using End Sub Public Sub CalculateClearance(ByVal trans As Transaction) pm = New ProgressMeter 'find all object endpoints closests to or furthest from radius center If CenterCar IsNot Nothing Then pm.Start(Me.Degree.ToString & "° Curve - Center Car") ProcessBlockReferenceForDistance(CenterCar.BlockRef, trans) pm.Stop() End If If RightCar IsNot Nothing Then pm.Start(Me.Degree.ToString & "° Curve - Right Car") ProcessBlockReferenceForDistance(RightCar.BlockRef, trans) pm.Stop() End If If LeftCar IsNot Nothing Then pm.Start(Me.Degree.ToString & "° Curve - Left Car") ProcessBlockReferenceForDistance(LeftCar.BlockRef, trans) pm.Stop() End If For Each load As ClearanceLoad In Loads pm.Start(Me.Degree.ToString & "° Curve - Load Block") ProcessBlockReferenceForDistance(load.BlockRef, trans) pm.Stop() Next 'use max distance value for max clearance MaxClearanceOffset = maxDist SetMaxClearanceDynProperty(trans) pm = Nothing End Sub Private Sub ProcessBlockReferenceForDistance(ByVal br As BlockReference, ByVal trans As Transaction, Optional ByVal mMasters As List(Of Matrix3d) = Nothing) Using trans2 As Transaction = trans.TransactionManager.StartTransaction Dim pTest As Point3d Using btr As BlockTableRecord = trans2.GetObject(br.BlockTableRecord, OpenMode.ForRead) Dim mBlockRef As Matrix3d = br.BlockTransform If mMasters Is Nothing Then 'first run through - at top level block here mMasters = New List(Of Matrix3d) pm.SetLimit(GetEntityCount(btr, trans2)) End If mMasters.Insert(0, mBlockRef) Dim pRadCenterTrans As Point3d = ReverseTransformPoint(radCenter, mMasters) For Each oid As ObjectId In btr pm.MeterProgress() Using ent As Entity = trans2.GetObject(oid, OpenMode.ForRead) If Not ent.Layer.ToUpper.Contains("NOPLOT") Then If Not ent.Layer.Contains("03 CEN") Then If Not ent.Linetype.ToUpper.Contains("CENTER") Then If TypeOf (ent) Is Line Then ''get endpoints of line and compare to radius center Dim eLine As Line = ent TestLine(eLine, mMasters, pRadCenterTrans) eLine = Nothing ElseIf TypeOf (ent) Is Circle Then 'use a distance to center type calculation Dim cir As Circle = ent pTest = cir.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) 'close point pTest = cir.Center.Add(pTest.GetVectorTo(cir.Center)) TestDistance(pTest, mMasters) 'far point cir = Nothing ElseIf TypeOf (ent) Is Arc Then 'get endpoints and midpoint and compare to radius center Dim eArc As Arc = ent TestArc(eArc, mMasters, pRadCenterTrans) eArc = Nothing ElseIf TypeOf (ent) Is Polyline Then 'get each vertext and compare to radius center Dim pline As Polyline = ent TestPolyLine(pline, mMasters, pRadCenterTrans) pline = Nothing ElseIf TypeOf (ent) Is Polyline2d Then Dim pline2d As Polyline2d = ent TestPolyLine2D(pline2d, mMasters, pRadCenterTrans) pline2d = Nothing ElseIf TypeOf (ent) Is Polyline3d Then Dim pline3d As Polyline3d = ent TestPolyLine3D(pline3d, mMasters, pRadCenterTrans) pline3d = Nothing ElseIf TypeOf (ent) Is Spline Then 'convert to polyline and compare to radius center Dim sPline As Spline = ent Dim p3Coll As Point3dCollection Dim pLine As Polyline If sPline.Type = SplineType.ControlPoints Then sPline = trans2.GetObject(sPline.Id, OpenMode.ForWrite) sPline.Type = SplineType.FitPoints End If Try pLine = sPline.ToPolylineWithPrecision(50) TestPolyLine(pLine, mMasters, pRadCenterTrans) Catch ex As System.Exception p3Coll = sPline.FitData.GetFitPoints() For index As Integer = 0 To p3Coll.Count - 1 pTest = p3Coll(index) TestDistance(pTest, mMasters) Next End Try 'test for bridging points pTest = sPline.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) sPline = Nothing p3Coll = Nothing pLine = Nothing ElseIf TypeOf ent Is BlockReference Then Dim brSub As BlockReference = ent Dim btrSub As BlockTableRecord = trans2.GetObject(brSub.BlockTableRecord, OpenMode.ForRead) If Not btrSub.Name = CurveCarBlockName Then 'loop on your sub blocks ProcessBlockReferenceForDistance(brSub, trans2, mMasters) End If brSub = Nothing btrSub = Nothing End If End If End If End If End Using Next mMasters.RemoveAt(0) mBlockRef = Nothing End Using pTest = Nothing End Using End Sub Private Sub TestLine(ByVal eLine As Line, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) Dim pTest As Point3d ''get endpoints of line and compare to radius center pTest = eLine.StartPoint TestDistance(pTest, mMasters) pTest = eLine.EndPoint TestDistance(pTest, mMasters) 'closest bridging point pTest = eLine.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) pTest = Nothing End Sub Private Sub TestArc(ByVal eArc As Arc, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) 'get endpoints and midpoint and compare to radius center Dim pTest As Point3d 'get a point closest to center, just like a circle pTest = eArc.GetClosestPointTo(pRadCenterTrans, True) 'add the vector of this point to the center of the arc pTest = eArc.Center.Add(pTest.GetVectorTo(eArc.Center)) 'qualify point pTest = eArc.GetClosestPointTo(pTest, False) TestDistance(pTest, mMasters) 'closest bridging point pTest = eArc.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) pTest = Nothing End Sub Private Sub TestPolyLine(ByVal pLine As Polyline, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) Dim pTest As Point3d Dim plExplode As New DBObjectCollection pline.Explode(plExplode) TestDBObjectCollection(plExplode, mMasters, pRadCenterTrans) 'test for briding points pTest = pline.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) pTest = Nothing plExplode = Nothing End Sub Private Sub TestPolyLine2D(ByVal pLine As Polyline2d, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) Dim pTest As Point3d Dim plExplode As New DBObjectCollection pLine.Explode(plExplode) TestDBObjectCollection(plExplode, mMasters, pRadCenterTrans) 'test for briding points pTest = pLine.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) pTest = Nothing plExplode = Nothing End Sub Private Sub TestPolyLine3D(ByVal pLine As Polyline3d, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) Dim pTest As Point3d Dim plExplode As New DBObjectCollection pLine.Explode(plExplode) TestDBObjectCollection(plExplode, mMasters, pRadCenterTrans) 'test for briding points pTest = pLine.GetClosestPointTo(pRadCenterTrans, False) TestDistance(pTest, mMasters) pTest = Nothing plExplode = Nothing End Sub Private Sub TestDBObjectCollection(ByVal plExplode As DBObjectCollection, ByVal mMasters As List(Of Matrix3d), ByVal pRadCenterTrans As Point3d) For Each dbo As DBObject In plExplode If TypeOf dbo Is Line Then Dim eLine As Line = dbo TestLine(eLine, mMasters, pRadCenterTrans) ElseIf TypeOf dbo Is Arc Then Dim eArc As Arc = dbo TestArc(eArc, mMasters, pRadCenterTrans) End If Next End Sub Private Sub TestDistance(ByVal pTest As Point3d, ByVal mMasters As List(Of Matrix3d)) pTest = TransformPoint(pTest, mMasters) Dim dTest As Double = pTest.DistanceTo(radCenter) 'if distance to center is greater than PlateCClearance then: If dTest > (Radius + TrackHalfWidth) Then 'positive distance 'remove the length to center and only use the offset distance dTest = dTest - (Radius + TrackHalfWidth) If dTest > maxDist Then maxDist = dTest maxPoint = pTest End If ElseIf dTest < (Radius - TrackHalfWidth) Then 'negative distance dTest = (Radius - TrackHalfWidth) - dTest If dTest > maxDist Then maxDist = dTest maxPoint = pTest End If End If dTest = Nothing End Sub Private Function TransformPoint(ByVal pTest As Point3d, ByVal mMasters As List(Of Matrix3d)) As Point3d For Each matrix As Matrix3d In mMasters pTest = pTest.TransformBy(matrix) Next Return pTest End Function Private Function ReverseTransformPoint(ByVal pTest As Point3d, ByVal mMasters As List(Of Matrix3d)) As Point3d If mMasters.Count > 0 Then For i As Integer = mMasters.Count - 1 To 0 Step -1 Dim matrix As Matrix3d = mMasters(i) matrix = matrix.Inverse pTest = pTest.TransformBy(matrix) Next End If Return pTest End Function Private Sub SetMaxClearanceDynProperty(ByVal trans As Transaction) 'use this to force the dynamic block from accidently calculating in the negative, which it may do if you set value to 0, due to double floating point rounding. If MaxClearanceOffset < 0.01 Then MaxClearanceOffset = 0.01 Else 'add one inch total to the offset for safety MaxClearanceOffset += ClearanceOffset End If Using trans2 As Transaction = trans.TransactionManager.StartTransaction If CurveBlock IsNot Nothing Then If CurveBlock.IsDynamicBlock Then Dim props As DynamicBlockReferencePropertyCollection = CurveBlock.DynamicBlockReferencePropertyCollection For Each prop As DynamicBlockReferenceProperty In props If prop.PropertyName = MaxClearanceOffsetDynPropertyName Then prop.Value = MaxClearanceOffset End If Next End If End If trans2.Commit() End Using End Sub
That's quite a bit of code. The only thing I can suggest is to test this without the progress meters - just to rule that out.
Thus far, I removed the progress meters, and set my spline to polyline prescision from 50 down to 10. While that did seem to speed up the processing a bit, niether effected the issue of out of control processing. After the routine finished, and I get command access back to AutoCAD, the ACAD.exe engine still runs at full core, and won't close down.
I may have to meticulously rem out code until i find the offender. Any other ideas?
jvj
hi i am not a expert but i have view fiew leak memory :
in SetCurveOffset :
Dim pm As New ProgressMeter
....
Public Sub CalculateClearance(ByVal trans As Transaction)
pm = New ProgressMeter
pm = Nothing
My question : where is the Dim for the PM... if it is the same as in SetCurveOffset you re init the pm . no?
For me, i dont launch transaction --> sub --> new transaction --> sub --> new transaction --> end tr --> return --> end tr --> return --> end tr..
I memorise the result of trans for launch another sub with trans in.
another 'bug' :
Private Sub ProcessBlockReferenceForDistance(ByVal br As BlockReference, ByVal trans As Transaction,
how can you pass a transaction with by val ? and the blockref too ? for me it is a ptr so it is by ref.
.... pm.SetLimit(GetEntityCount(btr, trans2)) --> another init ? can you stop and dispose then pm before re use it.
Sorry i am not a pro so i can say wrong.
++
hi. ok i dont know the ByVal for a pointer...
But how the garbage collector manage the copy of the pointer.... You have a prob of memory no ?
you use copy of pointer in all you sub/function. copy of transaction, of point3d etc...
Thank you for all your replys.
Ok here goes...
1. I remmed out all progress metering and still got this huge memory problem (i have 12 GB ram, shouldn't have any memory problem with ACAD files).
2. I created sub (nested) transactions particularly when I modify a block or dynamic block. These modifications must be committed before the rest of the code continues, or it may be using old data. The reason for the nested transaction is so that the entire command's processing can be undone in a single undo command.
3. After stepping through the code, running protions with or without the rest, and running large and small batches, the only thing I'm noticing is that larger batches take a disproportionately long time to process. On average I'm updating 36 dynamic blocks with 1 property which in the end result move 2 arcs each block. This usually has taken over 6 minutes to process. When I step through, and stopped the code between blocks, AutoCAD wouldn't get stuck on anything. But if I run it without the debugger (VS 2010 Pro) AutoCAD will get stuck in a loop that is not from my code. With VS 2010 I can hook into an already running AutoCAD and pause it. If it's running my code, then I get a line of code, if not I get 'external code'. When running away with itself I get external code on pauses.
Due to the large quantity of linework in the tested blocks, the processing is expected to take some time. If I run this on one block I don't get a problem, but when I run it on all 36 I will. I'm thinking there is some kind of cleanup or necessary pause and wait I can insert between blocks that may help this. Any other ideas?
jvj
ByVal on a collection object might as well be the same as ByRef, because it will only give me a new memory pointer to the same collection. It won't clone the collection. This is generally the same for complex object types. Transaction is one that works as a byref or byval with the same result.
jvj
Sorry no i have tested ByVal and ByRef
the Byref give you a pointer . if you modify the data, the new data is transfered
but
ByVal the new data is not transfered..
do ByVal Transaction --> you have a copy of transaction but i dont now if you commit the tr. if the commit is done for the tr before enter the sub...
If you ByVal a ptr of Point3D you cant change the data inside.. (the function point3D.X is readonly)
but if you use a string (ptr of string) you have a copy of the string not a ptr of the real string (you cant retrieve your new data outside of the sub).
Generally I agree with you, and I wouldn't willingly pass a transaction along as byval (unless I was just getting data, and not putting anything back to the drawing database). I'll make a pass through the code and look for the passing of transaction to subs and check for byval vs byref (preferred for transaction objects), but I doubt that will have anything to do with my issue.
Thanks,
jvj
From Microsoft, this may be interesting:
Although the passing mechanism can also affect the performance of your code, the difference is usually insignificant. One exception to this is a value type passed ByVal. In this case, Visual Basic copies the entire data contents of the argument. Therefore, for a large value type such as a structure, it is more efficient to pass it ByRef.
In a routine that processes so much stuff switching as much as I can over to byRef, could speed things up. I'll let you know how it turns out.
Thanks,
jvj
Well Byref had no effect as I expected, save one, I used half as much memory from 11gb down to 5gb. With the file open and doing nothing in AutoCAD I started at 0.7GB, so talk about your bloat. There is one thing I'd like to investigate. The debugger keeps poping up this message:
ContextSwitchDeadlock was detected Message: The CLR has been unable to transition from COM context 0x477358 to COM context 0x476d90 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.
I know little to nothing of multi-threaded documents, and weather or not I have any control over such things when working with AutoCAD .Net API. So this may be a white unicorn chase.
jvj
First of all, you should read my posts on AutoCAD Performance, it starts off slow but gets to the points you need to read about http://adndevblog.typepad.com/autocad/2012/07/the-right-tools-for-the-job-autocad-part-1.html?cid=6a...
For the code you have listed, you should not mutiply call StartTransaction as it is really inefficient. You should change your code to either pass the currently started Transaction, or change to StartOpenCloseTransaction.
Finally, you should read this post about calling Dispose() http://adndevblog.typepad.com/autocad/2012/07/forcing-the-gc-to-run-on-the-main-thread.html
Can't find what you're looking for? Ask the community or share your knowledge.