Как я могу убедиться, что объекты правильно утилизируются в .NET? - PullRequest
7 голосов
/ 02 мая 2009

Я создал приложение Windows Forms в .NET 2 с использованием C #, которое работает непрерывно. Для большинства аккаунтов я доволен этим, но мне сообщили, что он иногда терпел неудачу. Я могу контролировать его производительность в течение 50% времени и никогда не замечал сбоев.

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

Каковы оптимальные методы для правильной утилизации созданных объектов, которые создали таймеры и графические объекты, такие как графические пути, SQL-соединения и т. Д., Или я могу положиться на метод dispose, чтобы позаботиться обо всей сборке мусора?

Также: Есть ли способ контролировать ресурсы, используемые приложением?

Ответы [ 7 ]

14 голосов
/ 02 мая 2009

Рекомендуется убедиться, что все объекты, реализующие интерфейс IDisposable , называются Dispose on, как только объект больше не требуется.

Это можно сделать либо с помощью с использованием ключевого слова , либо с помощью try / finally конструкций.

В форме WinForms, у которой есть ресурсы, выделенные для времени жизни формы, необходим несколько иной подход. Поскольку сама форма реализует IDisposable, это указывает на то, что в какой-то момент времени Dispose будет вызываться в этой форме. Вы хотите убедиться, что ваши одноразовые ресурсы утилизируются одновременно. Для этого вам необходимо переопределить формы Dispose (bool dispose) метод. Реализация должна выглядеть примерно так:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        // dispose managed resources here
    }
    // dispose unmanaged resources here
}

Примечание по компонентам в формах: если ваш объект реализует интерфейс IComponent , вы можете поместить экземпляр в формы Контейнер . Контейнер позаботится об утилизации компонентов при утилизации самого контейнера.

4 голосов
/ 02 мая 2009

В дополнение к тому, что уже было сказано, если вы используете COM-компоненты, очень полезно убедиться, что они полностью выпущены. У меня есть фрагмент, который я постоянно использую для релизов COM:

private void ReleaseCOMObject(object o)
{
   Int32 countDown = 1;
   while(countDown > 0)
       countDown = System.Runtime.InteropServices.Marshal.ReleaseCOMObject(o);
}
2 голосов
/ 03 мая 2009

Есть несколько способов обеспечить это. Основная помощь, которую я нахожу, заключается в использовании ключевого слова "using". Это применяется как таковое:

using(SqlConnection connection = new SqlConnection(myConnectionString))
{
    /* utilise the connection here */ 
}

Это в основном означает:

SqlConnection connection = null;
try
{
    connection = new SqlConnection(myConnectionString);
}
finally
{
    if(connection != null) connection.Dispose();
}

Как таковой, он работает только с типами, которые реализуют IDisposable.

Это ключевое слово очень полезно при работе с объектами GDI, такими как ручки и кисти. Однако есть сценарии, в которых вы хотите удерживать ресурсы в течение более длительного периода времени, чем просто текущая область действия метода. Как правило, лучше избегать этого, если это возможно, но, например, при работе с SqlCe более эффективно поддерживать одно соединение с БД постоянно открытым. Поэтому никто не может избежать этой потребности.

В этом сценарии вы не можете использовать «использование», но вы все еще хотите иметь возможность легко восстановить ресурсы, удерживаемые соединением. Есть два механизма, которые вы можете использовать, чтобы вернуть эти ресурсы.

Один через финализатор. Все управляемые объекты, находящиеся вне области видимости, в конечном итоге собираются сборщиком мусора. Если вы определили финализатор, GC вызовет его при сборе объекта.

public class MyClassThatHoldsResources
{
    private Brush myBrush;

    // this is a finaliser
    ~MyClassThatHoldsResources()
    {
       if(myBrush != null) myBrush.Dispose();
    }
}

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

Другая проблема с финализатором заключается в том, что он не является детерминированным. Скажем, например, у меня есть класс, который общается через последовательный порт. Только одно соединение с последовательным портом может быть открыто одновременно. Поэтому, если у меня есть следующий класс:

class MySerialPortAccessor
{
    private SerialPort m_Port;

    public MySerialPortAccessor(string port)
    {
        m_Port = new SerialPort(port);
        m_Port.Open();
    }

    ~MySerialPortAccessor()
    {
        if(m_Port != null) m_Port.Dispose();
    }
}

Тогда, если бы я использовал объект следующим образом:

public static void Main()
{
    Test1();
    Test2();
}

private static void Test1()
{
    MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
    // do stuff
}

private static void Test2()
{
    MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
    // do stuff
}

У меня была бы проблема. Проблема в том, что финализатор не является детерминированным. То есть я не могу гарантировать, когда он запустится и, следовательно, приступить к утилизации моего объекта последовательного порта. Поэтому, когда я запускаю тест 2, я могу обнаружить, что порт все еще открыт. Хотя я мог вызывать GC.Collect () между Test1 () и Test2 (), что решило бы эту проблему, не рекомендуется . Если вы хотите добиться максимальной производительности коллектора, позвольте ему делать свое дело.

Поэтому то, что я действительно хочу сделать, это:

class MySerialPortAccessor : IDispable
{
    private SerialPort m_Port;

    public MySerialPortAccessor(string port)
    {
        m_Port = new SerialPort(port);
        m_Port.Open();
    }

    public void Dispose()
    {
        if(m_Port != null) m_Port.Dispose();
    }
}

И я перепишу свой тест так:

public static void Main()
{
    Test1();
    Test2();
}

private static void Test1()
{
    using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
    {
        // do stuff
    }
}

private static void Test2()
{
    using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
    {
        // do stuff
    }
}

Теперь это будет работать. Так что из финализатора? Зачем его использовать?

Неуправляемые ресурсы и возможные реализации, которые не вызывают Dispose.

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

Итак, в заключение, шаблон, рекомендованный в книге .NET Framework Guidelines , должен реализовать оба варианта следующим образом:

public void SomeResourceHoggingClass, IDisposable
{
    ~SomeResourceHoggingClass()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    // virtual so a sub class can override it and add its own stuff
    //
    protected virtual void Dispose(bool deterministicDispose)
    {    
         // we can tidy managed objects
         if(deterministicDispose)
         {
              someManagedObject.Parent.Dispose();
              someManagedObject.Dispose();
         }

         DisposeUnmanagedResources();

         // if we've been disposed by .Dispose()
         // then we can tell the GC that it doesn't
         // need to finalise this object (which saves it some time)
         //
         GC.SuppressFinalize(this);
    }
}
2 голосов
/ 02 мая 2009

Вы должны позвонить Dispose на ограниченные ресурсы, чтобы освободить их. Вы можете использовать оператор using по этому вопросу:

using (var resource = new MyScarceObject()) 
{
    // resource will be used here...
} // will free up the resources by calling Dispose automatically
1 голос
/ 02 мая 2009

Когда объект недоступен, вызывается метод Object.Finalize . Полезно подавить этот ненужный вызов в ваших классах, которые реализуют IDisposable. Вы можете сделать это, вызвав метод GC.SuppressFinalize

public void Dispose()
{
    // dispose resources here

    GC.SuppressFinalize(this);
}
1 голос
/ 02 мая 2009

Что касается мониторинга встроенного perfmon ( 2 ), то он отлично работает для использования памяти и т. Д. Если вы беспокоитесь о дескрипторах файлов, dll и т. Д., Я рекомендую Process Explorer и Process Monitor

1 голос
/ 02 мая 2009

Несколько советов:

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

http://msdn.microsoft.com/en-us/library/yh598w02.aspx

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...