Что происходит с объектом IDisposable, созданным в середине оператора, для которого я не могу выполнить явный вызов Dispose ()? - PullRequest
3 голосов
/ 06 января 2009

Скажем, я работаю с Sharepoint (это относится и к другим объектным моделям), и в середине своего выступления я вызываю метод, в данном случае «OpenWeb ()», который создает IDisposable объект SPWeb. Теперь я не могу вызвать Dispose () для объекта SPWeb, потому что у меня нет ссылки на него. Так нужно ли мне беспокоиться об этой утечке памяти?

SPUser spUser = SPControl.GetContextSite(HttpContext.Current).OpenWeb().SiteUsers[@"foo\bar"];

Я знаю, что мог разбить инструкцию на несколько строк и получить ссылку SPWeb для вызова Dispose:

SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb();
SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
spWeb.Dispose();

Пожалуйста, имейте в виду, что мой вопрос не об эстетике, а о том, что происходит с объектом IDisposable, для которого я не могу явно вызвать Dispose (), поскольку у меня нет ссылки.

Извините, что не достаточно ясно, когда я впервые задал вопрос. С тех пор я перефразировал это. Спасибо за все ответы до сих пор.

Ответы [ 9 ]

5 голосов
/ 06 января 2009

"что происходит с объектом IDisposable, для которого я не могу явно вызвать Dispose ()?"

Как правило, вы можете вызывать Dispose (неявно с помощью оператора using или явно) для всех одноразовых объектов, однако в гипотетическом сценарии, когда вы не можете , зависит от способ реализации объекта.

В общем случае .Net объекты будут следовать шаблону вдоль этих линий . Шаблон должен определить финализатор, который очищает вещи в случае, если утилита dispose не вызывается, а затем должен утилизировать подавление финализатора. Что снижает нагрузку на память и дает GC меньше работы.

Среди многих проблем с вызовом Dispose из финализатора является то, что вы превращаете однопотоковую проблему в многопоточную проблему, финализатор будет работать в другом потоке , который может предоставлять очень тонкие и сложные ловить жуков. Кроме того, это означает, что вы будете удерживать неуправляемые ресурсы дольше, чем ожидалось (например, вы открываете файл, забываете вызывать закрыть или утилизировать и в следующий раз, когда вы открываете его, он заблокирован)

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

Я обычно нахожу код гораздо более читабельным, когда располагаю свои объекты с помощью шаблона "using". Проблема с явным вызовом Dispose (object.Dispose ()) состоит в том, что может быть трудно отследить, где был размещен объект, и его очень легко забыть. Вы не можете забыть закрыть фигурную скобку оператора using, компилятор будет жаловаться :)

РЕДАКТИРОВАТЬ / GOTCHA

В соответствии с документацией MS Вы не должны вызывать dispose для ссылок на общие объекты точки совместного использования, которые возвращаются GetContextSite. Итак, вы должны быть очень осторожны здесь.

См. в этом ответе , где указан безопасный шаблон распределения, который вы должны использовать.

Однако, если у вас есть ссылка на общий ресурс, например, когда объект предоставляется Метод GetContextSite в веб-части, не используйте ни один из методов, чтобы закрыть объект. Используя любой метод на общий ресурс вызывает доступ Произошла ошибка нарушения. В сценариях где у вас есть ссылка на общий ресурс, вместо этого пусть Windows SharePoint Services или ваш портал Приложение для управления объектом.

4 голосов
/ 06 января 2009

Следующий текст более идиоматичен и лучше читается:

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
}
3 голосов
/ 06 января 2009

Я бы предложил разделить строку и использовать Dispose. Если объект реализует IDisposable, вы должны предположить, что он требует удаления, и, следовательно, использовать его в блоке using.

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SpUser spUser = null;
    if (spWeb != null)
    {
        spUser = spWeb.SiteUsers[@"foo\bar"];
    }
}

Делая это, вы получаете Dispose для объекта, а также обрабатывает ошибки в вызове OpenWeb (), который открывает внешний ресурс.

3 голосов
/ 06 января 2009

Вы должны явно (или неявно через с помощью оператора ) вызвать метод Dispose. Еще одна причина разбить код на несколько строк:

  • читаемость
  • проще отлаживать

Метод dispose можно выполнить в финализаторе, но безопаснее вызывать его самостоятельно.

1 голос
/ 19 января 2009

Многие ответы здесь предполагают, что важно только то, что Dispose вызывается в конце концов. Однако при работе с SPSite и SPWeb вам определенно необходимо вызвать Dispose () как можно скорее. Определить , когда вам нужно, часто сложно, но есть много хороших ссылок , которые помогут ответить на этот вопрос.

Что касается того, почему это так, Стефан Госснер дает отличную сводку здесь :

Каждый объект SPWeb и SPSite содержит ссылка на объект SPRequest, который содержит ссылку на SharePoint COM объект, который несет ответственность за общаться с бэкэндом SQL сервер.

Удаление объекта SPWeb не приведет к фактически удалить объект SPWeb из память (на самом деле .NET Framework не позволяет удалить любой объект из памяти детерминистическим способом) но это вызовет метод SPWeb объект, который вызывает COM-объект закрыть соединение с сервером SQL и освободить выделенную память.

Это означает, что соединение с внутренний сервер SQL останется открытым с момента объекта SPRequest был создан до объекта SPWeb расположен.

Лучшая практика для вашего примера кода будет выглядеть так:

SPSite contextSite = SPControl.GetContextSite(HttpContext.Current);
using (SPWeb spWeb = contextSite.OpenWeb())
{
  SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
  // Process spUser
}
// DO NOT use spUser
// DO NOT dispose site from HttpContext

Обратите внимание, что после удаления их родительского SPWeb небезопасно использовать объекты SP, такие как SPUser, SPList и т. Д.

1 голос
/ 18 января 2009

Как уже было сказано некоторыми: вы никогда не должны располагать объекты, которые вы не создаете. Поэтому в вашем примере вы должны , а не располагать объект SPWeb или SPSite!

Это уничтожит текущий объект SPRequest. Может показаться, что он все еще работает, но если вы, например, добавите новые веб-части позже или попытаетесь открыть панель инструментов для вашей веб-части, вы получите всевозможные странные ошибки.

Как уже было сказано, необходимо располагать экземплярами SPWeb и SPSite , которые вы создаете сами (новый).

Это может быть сделано либо с использованием (), либо с помощью try / finally (именно так оператор using () будет все равно отображаться в вашем коде MSIL). Если вы используете try / finally, лучше всего проверять наличие нулевого значения в вашем экземпляре SPWeb / SPSite и сначала проверять наличие SPWeb, поскольку SPSite автоматически удалит ваш SPWeb.

Еще одна важная вещь, которую следует помнить, когда вы зацикливаете элементы SPWebCollections, такие как AllWebs или Webs, - это распоряжаться вашими веб-сайтами, когда вы их просматриваете. Если имеется множество дочерних веб-сайтов, и вы работаете на 32-битном оборудовании с ограниченным потенциалом памяти, вы действительно можете очень быстро заполнить свою память объектами SPRequest. Это приведет к снижению производительности, поскольку пул приложений будет регулярно перезагружаться.

Когда это сказано, хорошей практикой также не является объединение вызовов, как вы делаете в своем примере кода. Трудно читать, и , если вы работали с SPWeb, которым вы должны располагать, вы не могли бы! Подобные утечки памяти труднее всего обнаружить, поэтому не делайте этого; -)

Я могу порекомендовать блог Роджера Лэмба для деталей: http://blogs.msdn.com/rogerla Также некоторые технические подробности в блоге Стефана Госнера: http://blogs.technet.com/stefan_gossner/archive/2008/12/05/disposing-spweb-and-spsite-objects.aspx

НТН Андерс

1 голос
/ 06 января 2009

Утечка памяти? Нет, вам не стоит об этом беспокоиться, если предположить, что реализация IDisposable соответствует рекомендациям библиотеки классов, поскольку следующая сборка мусора должна ее очистить.

Тем не менее, это выявляет ошибку в вашем коде, поскольку вы неправильно управляете временем жизни реализаций IDisposable, с которыми вы работаете (я подробно остановился на этой проблеме на http://www.caspershouse.com/post/A-Better-Implementation-Pattern-for-IDisposable.aspx). хороший первый шаг, но вы не гарантируете вызов Dispose в случае сбоя вызова SiteUsers.

Ваш исправленный код будет выглядеть так:

// Get the site.
var contextSite = SPControl.GetContextSite(HttpContext.Current);

// Work with the web.
using (SPWeb web = contextSite.OpenWeb())
{
  // Get the user and work with it.
  SPUser spUser = web.SiteUsers[@"foo\bar"];
}
0 голосов
/ 22 января 2009

Кажется, никто еще не добавил это:

Если ваш объект требует диспозитора (то есть захватывает ресурсы, которые должны быть освобождены), тогда вы должны реализовать финализатор, который вызывает метод диспозитора.

В диспетчере вы можете добавить строку:

System.GC.SuppressFinalize(this)

Что предотвратит финализацию, если вы позвоните диспозитору. Таким образом, вы можете красиво использовать свой объект, если это необходимо, но гарантируете, что он очищается после себя через финализатор (что и является причиной того, что в C # есть финализатор).

0 голосов
/ 18 января 2009

С http://msdn.microsoft.com/en-us/library/aa973248.aspx Рекомендации: использование одноразовых объектов Windows SharePoint Services :

Если объект SPSite получен из SPControl.GetContextSite, вызывающее приложение НЕ должно избавляться от объекта. Поскольку объекты SPWeb и SPSite хранят внутренний список, полученный таким образом, удаление объекта может привести к непредсказуемому поведению объектной модели SharePoint.

...