Проблема утилизации розетки / финализации дважды? - PullRequest
2 голосов
/ 08 января 2009

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

Проблема проявляется как «System.CannotUnloadAppDomainException: ошибка при выгрузке appdomain» в xunit, а внутреннее исключение - «System.ObjectDisposedException», которое выдается (по существу) внутри финализатора при закрытии сокета! Нет другой ссылки на сокет, который вызывает close и dispose защищен в классе Socket, поэтому я не уверен, как еще объект может быть удален.

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

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

Мои знания о сокетах - это только то, что я узнал с момента обнаружения этой проблемы, поэтому я не знаю, предоставил ли я все, что может понадобиться SO. ЛМК если нет!

public class Foo
{
    private Socket sock = null;
    private Thread tListenerThread = null
    private bool bInitialised;
    private Object InitLock = null;
    private Object DeInitLock = null;

    public Foo()
    {
        bInitialised = false;

        InitLock = new Object();
        DeInitLock = new Object();
    }

    public bool initialise()
    {
        if (null == InitLock)
            return false;

        lock (InitLock)
        {
            if (bInitialised)
                return false;

            sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 8);
            sock.Bind( /*localIpEndPoint*/);
            sock.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(mcIP));

            tListenerThread = new Thread(new ThreadStart(listener));
            tListenerThread.Start();

            bInitialised = true;
            return true;
        }
    }

    ~Foo()
    {
        if (bInitialised)
            deInitialise();
    }

    private void deInitialise()
    {
        if (null == DeInitLock)
            return;

        lock (DeInitLock)
        {
            if (bInitialised)
            {
                sock.Shutdown(SocketShutdown.Both); //throws System.ObjectDisposedException
                sock.Close();

                tListenerThread.Abort(); //terminates xunit test!
                tListenerThread = null;

                sock = null;

                bInitialised = false;
            }
        }
    }
}

Ответы [ 3 ]

8 голосов
/ 08 января 2009

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

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

(Я знаю, что вы пояснили в начале, что это не ваш код - я просто подумал, что объясню, почему это проблематично. Извиняюсь, если вы уже знали некоторые / все это.)

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

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

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

Я настоятельно рекомендую вам прочитать эту очень важную статью Microsoft для более подробной информации о том, как работает сборка мусора. Кроме того, это справочник MSDN по Реализация финализации и утилизации для очистки неуправляемых ресурсов , внимательно посмотрите рекомендации внизу.

В двух словах:

  • Если ваш объект содержит неуправляемый ресурс, вы должны реализовать IDisposable, и вы должны реализовать Finalizer.
  • Если ваш объект содержит объект IDiposable, он должен также самостоятельно реализовать IDisposable и явно распоряжаться этим объектом.
  • Если ваш объект содержит как неуправляемый, так и одноразовый предмет, финализатор должен вызвать две разные версии Dispose, одна из которых выпускает одноразовую и неуправляемую, а другая - только неуправляемую. Обычно это делается с помощью функции Dispose (bool), вызываемой Dipose () и Finalizer ().
  • Финализатор никогда не должен использовать какой-либо другой ресурс, кроме неуправляемого ресурса и самого себя. Несоблюдение этого требования может привести к ссылкам на собранные или удаленные объекты, поскольку объект временно ресурируется до завершения.
0 голосов
/ 08 января 2009

Новая информация: похоже, у меня две проблемы, но одна нить кажется довольно токсичной.

Из ссылки MSDN выше:

"ThreadAbortException - это особый исключение, которое можно поймать, но это будет автоматически поднят снова в конец блока захвата. "

По этой ссылке также есть несколько очень интересных материалов сообщества, включая "Тема. Аборт - признак плохо разработанной программы" .

Так что, по крайней мере, у меня есть патроны, чтобы изменить это сейчас:)

...