Hot News:

Mit Unterstützung durch:

  Foren auf CAD.de (alle Foren)
  AutoCAD ObjectARX und .NET
  Riesige Speicherlast lässt AutoCAD abstürzen

Antwort erstellen  Neues Thema erstellen
CAD.de Login | Logout | Profil | Profil bearbeiten | Registrieren | Voreinstellungen | Hilfe | Suchen

Anzeige:

Darstellung des Themas zum Ausdrucken. Bitte dann die Druckfunktion des Browsers verwenden. | Suche nach Beiträgen nächster neuer Beitrag | nächster älterer Beitrag
  
Gut zu wissen: Hilfreiche Tipps und Tricks aus der Praxis prägnant, und auf den Punkt gebracht für Autodesk Produkte
Autor Thema:  Riesige Speicherlast lässt AutoCAD abstürzen (1424 mal gelesen)
mOfl
Mitglied



Sehen Sie sich das Profil von mOfl an!   Senden Sie eine Private Message an mOfl  Schreiben Sie einen Gästebucheintrag für mOfl

Beiträge: 22
Registriert: 25.09.2010

erstellt am: 12. Okt. 2010 00:44    Editieren oder löschen Sie diesen Beitrag!  <-- editieren / zitieren -->   Antwort mit Zitat in Fett Antwort mit kursivem Zitat    Unities abgeben: 1 Unity (wenig hilfreich, aber dennoch)2 Unities3 Unities4 Unities5 Unities6 Unities7 Unities8 Unities9 Unities10 Unities

Hallo,

ich bin mit meinem Programm ein ganzes Stück weitergekommen, jetzt eckt es allerdings schon wieder. Ich will alle Straßen (laut einer Access-Datenbank) des Layers "Flurstück" in den Layer "Straße" kopieren und eine Schraffur erzeugen. Die Verknüpfung zwischen Datenbank und Entity sind Objektdaten, die zu den Entities gespeichert werden, genauer geht es um die Spalte "Gemarkung". Dazu habe ich ein bisschen Code geschrieben, den ich unten angehängt habe.

Zum Code: Es ist eigentlich nur der erste Code-Abschnitt relevant, die anderen sind nur zum Nachvollziehen des Programmablaufs für Interessierte.

Ich habe eine Funktion checkObject(), die anhand der ObjectID einer Entity in deren Objektdaten schaut, ob sie mit einer gegebenen Gemarkung übereinstimmt:

Code:
        private bool checkObject(Tables tables, ObjectId id, string gemarkung) {
            try {
                using (Records records = tables.GetObjectRecords(0, id, Autodesk.Gis.Map.Constants.OpenMode.OpenForRead, false)) {
                    if (records.Count == 0) {
                        return false;
                    }

                    foreach (Record record in records) {
                        Autodesk.Gis.Map.ObjectData.Table table = tables[record.TableName];
                        FieldDefinitions tableDef = table.FieldDefinitions;

                        MapValue valueGemarkung = record[tableDef.GetColumnIndex("Gemarkung")];

                        string recordGemarkung = valueGemarkung.StrValue.Trim();

                        if (gemarkung.Equals(recordGemarkung)) {
                            return true;
                        }
                    }

                    return false;
                }
            }
            catch (System.Exception) {
                return false;
            }
        }



Dann habe ich die Hauptfunktion moveStrasse(), die die ganze Arbeit macht: Alten Layer ("Flurstück") suchen, neuen Layer ("Straße") suchen, für alle Entities auf dem alten Layer die Methode checkObject() aufrufen, um die Entity mit der gewünschten Gemarkung zu erhalten, Layer ändern, Schraffur erzeugen und anwenden und zuletzt die Schraffur nach ganz unten legen.

Code:
        private void moveStrasse(string layerAlt, string layerNeu, string gemarkung) {
            try {
                Tables tables = HostMapApplicationServices.Application.ActiveProject.ODTables;
                Transaction tr = AcadDB.TransactionManager.StartTransaction();
                LayerTable lt = (LayerTable) tr.GetObject(AcadDB.LayerTableId, Autodesk.AutoCAD.DatabaseServices.OpenMode.ForRead);
                BlockTable bt = (BlockTable) tr.GetObject(AcadDocument.Database.BlockTableId, Autodesk.AutoCAD.DatabaseServices.OpenMode.ForRead);
                BlockTableRecord btr = (BlockTableRecord) tr.GetObject(bt[BlockTableRecord.ModelSpace], Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);

                if ((!lt.Has(layerAlt)) | | (!lt.Has(layerNeu))) {
                    return;
                }
                else {
                    ObjectId layerAltId = lt[layerAlt];
                    ObjectId layerNeuId = lt[layerNeu];

                    TypedValue[] tvs = new TypedValue[1];
                    tvs[0] = new TypedValue((int) DxfCode.LayerName, layerAlt);

                    SelectionFilter sf = new SelectionFilter(tvs);
                    PromptSelectionResult psr = AcadEditor.SelectAll(sf);

                    Hatch hat = new Hatch();
                    hat.SetDatabaseDefaults();
                    hat.Layer = layerNeu;
                    hat.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");

                    ObjectId hatId = btr.AppendEntity(hat);
                    tr.AddNewlyCreatedDBObject(hat, true);

                    ObjectIdCollection ids = new ObjectIdCollection();

                    foreach (ObjectId id in psr.Value.GetObjectIds()) {
                        if (checkObject(tables, id, gemarkung)) {
                            Entity ent = (Entity) tr.GetObject(id, Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);
                            ent.LayerId = layerNeuId;

                            ids.Add(id);
                        }
                    }

                    hat.Associative = true;
                    hat.AppendLoop(HatchLoopTypes.Default, ids);
                    hat.EvaluateHatch(true);
                    btr.DowngradeOpen();

                    ids.Clear();
                    ids.Add(hatId);
                    DrawOrderTable dot = (DrawOrderTable) tr.GetObject(btr.DrawOrderTableId, Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);
                    dot.MoveToBottom(ids);
                }

                tr.Commit();
            }
            catch (System.Exception) {
                //
            }
        }



Ich habe mir einen Befehl gemacht, der die Funktion moveStrasse() mit einem fix gewählten Grundstück ausführt, was wie gewünscht funktioniert. Hier will ich erwähnen, dass der AutoCAD-Prozess bis zu diesem Zeitpunkt immer ziemlich genau 222 MB im Arbeitsspeicher belegt. Führe ich die Funktion aus, beträgt die Belegung im Arbeitsspeicher recht konstant etwa 277 MB. Also dieser eine Aufruf scheit ziemlich genau 55MB Arbeitsspeicher zu kosten. Sehr unangenehm, wenn ich weiß, dass 50 bis 100 verschiedene Straßen aussortiert werden sollen...

Code:
        [Autodesk.AutoCAD.Runtime.CommandMethodAttribute("istStrasse")]
        public void isStrasse() {
            moveStrasse("Flurstück", "Straße", "1147");
        }


Dann habe ich das ganze für den größeren Maßstab anlegen wollen. Es wird jetzt "Gemarkung" aus einer Access-Datenbank gelesen, wobei nur die wichtigen Zeilen der Tabelle, also die Straßen, berücksichtigt werden. Wieder wird moveStrasse() aufgerufen.

Code:
        [Autodesk.AutoCAD.Runtime.CommandMethodAttribute("strassenFiltern")]
        public void strassenFiltern() {
            myAccessDAO = new AccessDAOEB(AcadEditor);
            myStringHandler = new StringHandlerEB();

            string pfad = "C:\\prog\\test.mdb";
            string layerAlt = "Flurstück";
            string layerNeu = "Straße";

            if (myAccessDAO.open(pfad)) {
                System.Data.DataTable resFlst = myAccessDAO.getDataTable();

                if (resFlst == null) {
                    AcadEditor.WriteMessage("Fehler: Access-Datei konnte nicht gelesen werden.\n");
                }
                else {
                    Tables tables = HostMapApplicationServices.Application.ActiveProject.ODTables;

                    for (int i = 0; i < resFlst.Rows.Count; i++) {
                        try {
                            DataRow row = resFlst.Rows[i];
                            string gemarkung = myStringHandler.getInt(row[spalteGemarkung]).ToString();
                            string nutzung = myStringHandler.getString(row[spalteNutzung]);

                            if ((nutzung != null) && ((nutzung.Equals("Straße")) | | (nutzung.Equals("Strasse")))) {
                                moveStrasse(layerAlt, layerNeu, gemarkung);
                            }
                        }
                        catch (System.Exception ex) {
                            AcadEditor.WriteMessage("Fehler: " + ex.Message);
                        }
                    }
                }
            }
            else {
                AcadEditor.WriteMessage("Fehler: Access-Datei konnte nicht geöffnet werden.\n");
            }
        }



Hier liegt das eigentliche Problem: In Windeseile schießt die Speicherlast des Prozesses auf etwa 1300MB, AutoCAD reagiert nicht mehr und beendet sich dann mit einer Fehlermeldung. Durch schrittweises Aus- und Einkommentieren konnte ich als Übeltäter eindeutig die Funktion checkObject() ausmachen. Kommentiert man den foreach (Record record in records)-Block aus und gibt false zurück, läuft die Hauptfunktion komplett durch und der Prozess wiegt am Ende etwa 11 MB mehr, also im sehr akzeptablen Bereich - nur natürlich funktioniert es nicht ohne checkObject().

Was kann ich tun, um den Speicher nicht explodieren zu lassen? Ich habe gelesen, man könne/solle Objekte mit .Dispose() freigeben, aber jeder Versuch von mir, etwas mit Dispose() zu behandeln, führte zum Programmabsturz oder zu fehlerhaften Grafiken. Und sieht jemand, was genau eigentlich der Übeltäter ist? Denn selbst wenn man nur den Inhalt der Schleife wegkommentiert und foreach (Record record in records) {} stehen lässt, steigt der Speicherverbrauch bis zum Absturz. Aber werden hier irgendwo neue Objekte angelegt? Wenn ja, wie kann ich diese Löschen? Oder was kann ich sonst machen?

Ich hoffe, mir kann einer helfen.

Viele Grüße
mOfl

Eine Antwort auf diesen Beitrag verfassen (mit Zitat/Zitat des Beitrags) IP


Ex-Mitglied

erstellt am: 12. Okt. 2010 00:57    Editieren oder löschen Sie diesen Beitrag!  <-- editieren / zitieren -->   Antwort mit Zitat in Fett Antwort mit kursivem Zitat

Hi,

probier mal diesen Code:
foreach (Record record in records) {....

umzubauen in einen Zähler einer Integer-Variablen und so auf die Collection der Records hinzugreifen

Code:
for(i as integer = 0; i < records.count; i++)
{
   using rec as record = records(i)
   { ....
   }
}

Damit wird auch 'rec' wirklich mit .Dispose behandelt

Hilft das alles nichts, gibt's noch den GC, mit Vorsicht zu geniessen, aber bei Speicherproblemen durchaus brauchbar

Code:
GC.Collect;
GC.WaitForPendingFinalizers;

HTH, - alfred -

------------------
www.hollaus.at

mOfl
Mitglied



Sehen Sie sich das Profil von mOfl an!   Senden Sie eine Private Message an mOfl  Schreiben Sie einen Gästebucheintrag für mOfl

Beiträge: 22
Registriert: 25.09.2010

erstellt am: 12. Okt. 2010 20:47    Editieren oder löschen Sie diesen Beitrag!  <-- editieren / zitieren -->   Antwort mit Zitat in Fett Antwort mit kursivem Zitat    Unities abgeben: 1 Unity (wenig hilfreich, aber dennoch)2 Unities3 Unities4 Unities5 Unities6 Unities7 Unities8 Unities9 Unities10 Unities

Vielen Dank für die wieder sehr schnelle und sehr hilfreiche Antwort!
Die Verwendung von using-Blöcken hält den Speicher wirklich sehr sauber, jedoch hatte ich lange Zeit mit einer Exception zu kämpfen, die AutoCAD abstürzen ließ und im Dump auf eine Exception bei Dispose() von Records verwies. Nach sehr viel Testen habe ich jetzt bemerkt, dass die Exception nicht von using (Records records = ...) kommt, sondern von der Zeile

Code:
BlockTableRecord btr = (BlockTableRecord) tr.GetObject(bt[BlockTableRecord.ModelSpace], Autodesk.AutoCAD.DatabaseServices.OpenMode.ForWrite);

in moveStrasse(). Daraus auch einen using-Block gemacht, funktioniert jetzt alles, wie es soll! 

Gruß
mOfl

Eine Antwort auf diesen Beitrag verfassen (mit Zitat/Zitat des Beitrags) IP


Ex-Mitglied

erstellt am: 12. Okt. 2010 22:57    Editieren oder löschen Sie diesen Beitrag!  <-- editieren / zitieren -->   Antwort mit Zitat in Fett Antwort mit kursivem Zitat

Hi,

wenn ich es richtig lese, dann machst Du über TransAction den Zugriff auf den BlockTableRecord des Modellbereichs, dann aber:
btr.DowngradeOpen()

Das sind zwei unterschiedliche Zugriffsarten auf Datenbankobjekte (entweder Open/DowngradeOpen/Close oder via TransAction), diese zu mischen tut nie gut! Aber Du hast es für Dich gelöst, damit passt es ja. 

- alfred -

------------------
www.hollaus.at

Anzeige.:

Anzeige: (Infos zum Werbeplatz >>)

Darstellung des Themas zum Ausdrucken. Bitte dann die Druckfunktion des Browsers verwenden. | Suche nach Beiträgen

nächster neuerer Beitrag | nächster älterer Beitrag
Antwort erstellen


Diesen Beitrag mit Lesezeichen versehen ... | Nach anderen Beiträgen suchen | CAD.de-Newsletter

Administrative Optionen: Beitrag schliessen | Archivieren/Bewegen | Beitrag melden!

Fragen und Anregungen: Kritik-Forum | Neues aus der Community: Community-Forum

(c)2023 CAD.de | Impressum | Datenschutz