В данной заметке речь пойдёт об использовании IDisposable в AutoCAD .NET API. Если не освобождать ресурсы, то будет происходить утечка памяти, которая может привести к серьёзным проблемам. Дабы этого не происходило, нужно понимать, для каких объектов и когда следует вызывать Dispose(), а так же для каких объектов этого делать категорически нельзя.
- Cleaning up after yourself: how and when to dispose of AutoCAD objects in .NET
- Calling Dispose() on AutoCAD objects
- Examples of calling Dispose() on AutoCAD objects
В AutoCAD класс DBObject реализовывает интерфейс IDisposable. Соответственно, указанный интерфейс реализуется и всеми классами, унаследованными от DBObject. Этот же интерфейс реализован и классами Document и Database, однако для Document вызывать Dispose() никогда не следует (это делает сам AutoCAD), а так же нельзя вызывать Dispose() для объекта, хранящегося в свойстве Document.Database.
"Вручную" вызывать Dispose() следует в следующих случаях:
- Для временных объектов, которые никогда не будут добавлены в базу данных чертежа. Например, это могут быть нерезидентные объекты, предоставленные через Autodesk.AutoCAD.Geometry.
- Для временных объектов, которые потенциально могут быть добавлены в базу данных чертежа, но тем не менее не были в неё добавлены.
- Если экземпляр класса Database был вами создан способом "new Database(...)" без открытия документа. При работе с созданным подобным образом объектом Database, необходимо не забывать предварительно инициализировать им свойство HostApplicationServices.WorkingDatabase, иначе можно получить исключение eWrongDatabase. По завершению работы с объектом, следует возвращать свойству HostApplicationServices.WorkingDatabase предыдущее значение.
Внимание!
Некоторые ресурсы, используемые экземплярами различных классов, могут быть разделяемыми, т.е. одним и тем же ресурсом пользуется сразу несколько разных объектов. К сожалению, в AutoCAD .NET API подобные ситуации могут привести к Fatal Error, если будет произведено освобождение такого общего ресурса в то время, как им продолжают пользоваться другие объекты.
Примечание 1
Скорее всего, возникновение обозначенной выше проблемы с освобождением совместно используемых ресурсов обусловлено плохо написанным кодом разработчиков Autodesk: по-нормальному, для каждого совместно используемого ресурса должен вестись свой счётчик ссылок. В коде метода Dispose() значение счётчика должно сначала декрементироваться (т.е. уменьшаться на 1) и затем проверяться на равенство 0. Если равенство не выполняется, значит ресурс используется кем-то ещё - в этом случае общий ресурс не освобождается. Но если текущее значение счётчика равно 0, то ресурс можно смело освобождать.
Учитывая возможные проблемы при освобождении совместно используемых ресурсов, в подобных ситуациях компания Autodesk рекомендует пользоваться "методом научного тыка" (см. вторую из обозначенных выше ссылок): если вдруг, добавив очередной вызов Dispose() вы обнаруживаете возникновение Fatal Error, то попробуйте закомментировать этот вызов. Если ошибка исчезла, значит в данном случае вызов Dispose() выполнять не следует (логично). Всех мест в API, где наблюдается проблема освобождения совместно используемых ресурсов, не знает даже Autodesk. Поэтому данные места вы порой будете определять экспериментально, методом проб и ошибок (к сожалению).
Ресурсы всех объектов, полученных при помощи объекта Transaction, посредством метода GetObject(), а так же ресурсы новых объектов, добавленных в базу данных чертежа посредством метода AddNewlyCreatedDBObject(), будут освобождены автоматически в коде метода Dispose() этого объекта транзакции. Т.е. для них не нужно вызывать Dispose() персонально.
Настоятельно не рекомендую инициализировать объект Transaction следующим образом:
1: // db - экземпляр Database
2: Transaction tr = db.TransactionManager.StartTransaction();
3: ...
4: tr.Commit();
5: tr.Dispose();
При такой форме записи, если между инициализацией экземпляра транзакции и вызовом его метода Dispose() произойдёт необработанное исключение, то его Dispose() вызван не будет. Соответственно не будут освобождены ресурсы, связанные с этим объектом, а так же не будут вызваны методы Dispose() для всех объектов, полученных из базы данных при помощи этого экземпляра транзакции, а так же методы Dispose() новых объектов, с его помощью добавленных в базу данных чертежа.
Дабы избежать обозначенной выше опасности, следует инициализацию объекта Transaction выполнять так:
1: // db - экземпляр Database
2: using(Transaction tr = db.TransactionManager.StartTransaction()){
3: ...
4: tr.Commit();
5: }
При таком способе, Dispose() гарантированно будет вызван при выходе из блока using, как для объекта транзакции, так и для всех объектов, работающих в её контексте, даже если произойдёт необработанное исключение.
Примечание 2
Использовать блок using следует и для инициализации объектов, созданных вне контекста транзакции, поскольку для них метод Dispose() вызывать необходимо вручную.
Дополнительная информация:
1 комментарий:
Замечательная статья, помогла понять почему при некоторых манипуляциях возникают ошибки.
Отправить комментарий