Transaction.Dispose() необходимо?

Автор Тема: Transaction.Dispose() необходимо?  (Прочитано 10142 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Тема содержит сообщение с Решением. Нажмите здесь чтобы посмотреть его.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Transaction.Dispose() необходимо?
« : 11-06-2019, 11:57:08 »
Сейчас разбирался с чужим кодом и обнаружил, что транзакция используется без блока using и без явного вызова Dispose(). При этом, Commit() вызывается. И вроде как приложение работает и даже без ошибок. И у меня закрались сомнения: Насколько необходимо вызывать Dispose() у транзакции? Что будет если этого не делать? Может быть, это не так уж необходимо?

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Transaction.Dispose() необходимо?
« Ответ #1 : 11-06-2019, 12:02:07 »
Что будет если этого не делать?
Скорее всего не будет происходить сборка мусора (не всегда стоит доверять автоматической сборке мусора, так как нет точного понятия, когда она происходит) и не будут освобождаться ресурсы = забивание памяти. Также, можно предположить - если транзакция в автокаде несет туже смысловую нагрузку, что и в базах данных, то возможно там "под капотом" существует что-то типа соединения с БД чертежа, которое закрывается в Dispose()
В общем - конечно можно и не использовать Dispose() (в любом примере, а не только касаемо транзакций в автокаде), но это может обернуться неожиданными проблемами. Причем (по закону подлости) в релизе где-то там далеко у пользователя ))

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Transaction.Dispose() необходимо?
« Ответ #2 : 11-06-2019, 12:07:48 »
Что будет если этого не делать? Может быть, это не так уж необходимо?
Итак. Если нет вызова Transaction.Commit, и нет Transaction.Dispose, то Transaction.Dispose будет вызван сборщиком мусора. Причем это может произойти в любой момент времени и даже в другом потоке, что вероятно приведет к краху AutoCAD. Напоминаю, что Transaction.Dispose в случае если не вызван Transaction.Commit приводит к Transaction.Abort и откат изменений.  Если Transaction.Commit вызывается, то Transaction.Dispose ничего не делает и соответственно всё должно завершится нормально.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Re: Transaction.Dispose() необходимо?
« Ответ #3 : 11-06-2019, 12:25:49 »
Просто источник кода довольно серьёзный. В другом случае я бы не сомневался, что это ошибка разработчика, а здесь засомневался. Получается что, оказывается, "так можно было"(с). Много лет просто на автомате писал этот блок
Код - C# [Выбрать]
  1. using (Transaction tr = db.TransactionManager.StartTransaction())
  2. {
  3. /*...*/
  4. tr.Commit();
  5. }
А оказывается, что это необязательно.

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Transaction.Dispose() необходимо?
« Ответ #4 : 11-06-2019, 12:28:24 »
А оказывается, что это необязательно.
Ну хуже точно не будет. Вообще, есть такое правило "Если объект реализует интерфейс IDisposable, всегда используй using" ))

Отмечено как Решение Дмитрий Загорулькин 11-06-2019, 13:03:48

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Transaction.Dispose() необходимо?
« Ответ #5 : 11-06-2019, 12:36:05 »
А оказывается, что это необязательно.
Это самая правильная форма записи. В частности при любом exception внутри using будет вызван tr.Dispose. В случае отсутствия using и try/catch/finally ты не можешь быть уверен, что будет вызван tr.Commit(), т.к. исключение может произойти раньше.

Вообще же tr.Dispose выполняет какие-то действия:

Код - C# [Выбрать]
  1. // Autodesk.AutoCAD.DatabaseServices.Transaction
  2. protected unsafe override void DeleteUnmanagedObject()
  3. {
  4.         this.CheckTopTransaction();
  5.         AcDbTransactionManager* expr_11 = <Module>.AcDbImpTransaction.transactionManager(this.GetImpObj());
  6.         int num = calli(Acad/ErrorStatus modopt(System.Runtime.CompilerServices.CallConvCdecl)(System.IntPtr), expr_11, *(*expr_11 + 72L));
  7.         if (num != 0)
  8.         {
  9.                 Interop.ThrowExceptionForErrorStatus(num);
  10.         }
  11. }
Что будет если этот код будет вызван не из основного потока AutoCAD при сборке мусора - предугадать сложно.

Просто источник кода довольно серьёзный.
Это может быть и учебный код, когда на такие вещи закрывают глаза, чтобы не потерялся основной алгоритм.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Re: Transaction.Dispose() необходимо?
« Ответ #6 : 11-06-2019, 12:57:10 »
Если объект реализует интерфейс IDisposable, всегда используй using
Эх! Если бы всё было так просто... Иногда следование этому правилу приводит к фатальным ошибкам. Потому что, как потом оказывается, этим объектом управляет какой-то другой внутренний механизм и когда его явно диспозишь, это вызывает крах.
Вообще же tr.Dispose выполняет какие-то действия:
Да, я тоже сейчас заглянул в библиотеки. При вызове Dispose() цепочка длинная получается, но в итоге действительно приходим к методу DeleteUnmanagedObject(). К сожалению, что там делается - не понять. Пожалуй, Вы правы - лучше вызывать явно в коде!
Это может быть и учебный код, когда на такие вещи закрывают глаза, чтобы не потерялся основной алгоритм.
Это в библиотеках Dynamo для Civil. У них там всё довольно запутано - обёртка на обёртке. Транзакция создаётся и коммитится внутри их библиотек, но почему-то не диспозится.
А стал разбираться потому, что словил исключение при закрытии Civil 3D:

Сейчас вот думаю - может быть по этой причине и выскакивает исключение?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Transaction.Dispose() необходимо?
« Ответ #7 : 11-06-2019, 13:01:53 »
Это в библиотеках Dynamo для Civil.
Ну это тоже не показатель крутизны. :-)
Сейчас вот думаю - может быть по этой причине и выскакивает исключение?
Вполне возможно.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Привалов Дмитрий

  • ADN Club
  • *****
  • Сообщений: 546
  • Карма: 119
Re: Transaction.Dispose() необходимо?
« Ответ #8 : 11-06-2019, 13:45:11 »
Иногда следование этому правилу приводит к фатальным ошибкам.
Кто купил книгу Полещука Н.Н. "Программирование для AutoCAD 2013-2015", со страницы 207 об этом хорошо описано.

Суть примерно такая:
Вызывай всегда, кроме случаев, когда объект создавал AutoCAD. Т.е. Document и Database открытые AutoCADом он и должен закрывать.
Если ты создал  пустую Database то должен закрыть ты.

Это самая правильная форма записи. В частности при любом exception внутри using будет вызван tr.Dispose. В случае отсутствия using и try/catch/finally ты не можешь быть уверен, что будет вызван tr.Commit(), т.к. исключение может произойти раньше.
Маленькое уточнение using за программиста оборачивает код в try/catch/finally т.е. это то же самое, только запись короче и читабельнее.
Если приложение вылетит и закроется то tr.Commit() может и не выполниться. Но в такой ситуации это уже не важно.

может быть по этой причине и выскакивает исключение?
Вполне может быть причиной. Как написали ранее сборщик мусора выполнит Dispose() практически в любом случае, но позже, например при закрытии Civil, когда уже все Database закрыты и из второго потока. Т.е. метод Dispose() Net Wrapper может попытаться обратиться к объекту, которого уже не существует.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Re: Transaction.Dispose() необходимо?
« Ответ #9 : 11-06-2019, 13:55:24 »
Вызывай всегда, кроме случаев, когда объект создавал AutoCAD. Т.е. Document и Database открытые AutoCADом он и должен закрывать.
Если ты создал  пустую Database то должен закрыть ты.
Опять же, очень упрощённая логика. Раз уж разговор изначально про транзакцию, то по этой логике AutoCAD должен её сам закрывать. Мы же не создаём её:
Код - C# [Выбрать]
  1. Transaction tr = new Transaction();
А создаёт её сам AutoCAD, если точнее - менеджер транзакций:
Код - C# [Выбрать]
  1. Transaction tr = db.TransactionManager.StartTransaction();
Так что, тут важно не кто создаёт объект, а область его использования. Если она выходит за рамки кода, то диспозить его не нужно.
Кстати, ещё пример вспомнил. В методе TransformOverrule.Explode можно изменить коллекцию получаемых после взрыва объектов. Для этого мы их создаём (не AutoCAD) и помещаем в выходную коллекцию. Попробуйте их диспозить, следуя логике "из книжки"  ;)

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Transaction.Dispose() необходимо?
« Ответ #10 : 11-06-2019, 14:02:09 »
Привалов Дмитрий, Дмитрий Загорулькин
В действительности универсального правила нет.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Привалов Дмитрий

  • ADN Club
  • *****
  • Сообщений: 546
  • Карма: 119
Re: Transaction.Dispose() необходимо?
« Ответ #11 : 11-06-2019, 14:11:14 »
А создаёт её сам AutoCAD, если точнее - менеджер транзакций:
Точнее ты говоришь менеджеру транзакции создать для тебя транзакцию.
AutoCAD ее видит, но не знает когда ее нужно закрыть.
Предположительно при закрытии Database AutoCAD должен сам закрыть все транзакции. Тут по логике все хорошо.

Но твой объект - NET Wrapper для Transaction может зависнуть в приложении и висеть долго, и когда сборщик мусора сам до него доберется, он обязательно вызовет метод Dispose(). А этот метод может не пытаться определить существует ли чертеж и его транзакции, а напрямую обращаться к объекту которого не существует.

Оффлайн Дмитрий ЗагорулькинАвтор темы

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Re: Transaction.Dispose() необходимо?
« Ответ #12 : 11-06-2019, 14:27:30 »
Точнее ты говоришь менеджеру транзакции создать для тебя транзакцию.
Это в данном случае мы уже по опыту знаем, что мы создаём транзакцию. А если следовать логике названий, то метод не создаёт транзакцию, а запускает. То есть, если бы было название CreateAndStartTransaction или StartNewTransaction, то уже было бы интуитивно понятно, что создаётся новый объект. Мне встречались и другие методы, которые в своём названии не содержат намёков на то, что новый объект создаётся. И даже случаи, когда новый объект создаётся при обращении к свойству! Созданные таким образом объекты нужно было самостоятельно уничтожать. С автокадом нужно всегда держать ухо востро - изучать документацию, лезть внутрь библиотек, проводить тесты. Практика показывает, что даже примерам от ADN и из справки нельзя верить! Они сами иногда не до конца понимают как нужно правильно с объектами автокада работать, пока жаренный петух не клюнет :)
Ну, собственно, Александр Наумович выше уже подытожил:
В действительности универсального правила нет.

Оффлайн Привалов Дмитрий

  • ADN Club
  • *****
  • Сообщений: 546
  • Карма: 119
Re: Transaction.Dispose() необходимо?
« Ответ #13 : 11-06-2019, 14:33:42 »
Так что, тут важно не кто создаёт объект, а область его использования. Если она выходит за рамки кода, то диспозить его не нужно.
Кстати, ещё пример вспомнил. В методе TransformOverrule.Explode можно изменить коллекцию получаемых после взрыва объектов. Для этого мы их создаём (не AutoCAD) и помещаем в выходную коллекцию. Попробуйте их диспозить, следуя логике "из книжки" 

Александр правильно говорит, особых правил нет
Метод Dispose() создается программистом, подразумевая что необходимо выполнить какие-то действия перед окончанием использования объекта. Например в метод могут запихнуть закрыть файл, подключение к БД, или просто оставить метод пустым.
Но не известно в какой момент программист подразумевал его запуск.

Одно лишь известно, что сборщик мусора при удалении объекта из памяти вызывает предварительно метод Dispose
Будет ли это корректно в момент вызова метода, зависит от реализации метода.

У AutoCAD все объекты унаследованы от DisposableWrapper, т.е. для всех них будет вызван метод Dispose(). Тут главный вопрос кто и когда будет его вызывать и единого правила нет.

Есть лишь общая рекомендация по IDisposable, что если видишь, что объект унаследован от интерфейса IDisposable, то значит программист подразумевал, что перед окончанием использования объекта должны производиться какие-то действия. Ели этот метод вызывает приложение, то не вызывай. Если сборщик мусора, то лучше ты, чем он.

Оффлайн Привалов Дмитрий

  • ADN Club
  • *****
  • Сообщений: 546
  • Карма: 119
Re: Transaction.Dispose() необходимо?
« Ответ #14 : 11-06-2019, 14:45:29 »
Это в данном случае мы уже по опыту знаем, что мы создаём транзакцию. А если следовать логике названий, то метод не создаёт транзакцию, а запускает. То есть, если бы было название CreateAndStartTransaction или StartNewTransaction, то уже было бы интуитивно понятно, что создаётся новый объект.

Может метод неудачно обозван, такое бывает, вот описание из arxdev.chm
The transaction manager maintains transactions in a stack, with the most recent transaction at the top of the stack. When you start a new transaction using AcTransactionManager::startTransaction(), the new transaction is added to the top of the stack and a pointer to it is returned (an instance of AcTransaction). When someone calls AcTransactionManager::endTransaction() or AcTransactionManager::abortTransaction(), the transaction at the top of the stack is ended or aborted.