Использовать Dispose () или финализатор для очистки управляемых потоков? - PullRequest
2 голосов
/ 16 сентября 2010

Предположим, у меня есть класс насоса сообщений в C ++ 0x, подобный следующему (обратите внимание, SynchronizedQueue - это очередь функции , и когда вы вызываете receive () в очереди, и она пуста, она блокирует вызывающий поток, пока не найдется элемент для возврата):

class MessagePump
{
 private:
    bool done_;
    Thread* thread_;
    SynchronizedQueue queue_;

    void Run()
    {
        while (!done)
        {
            function<void()> msg = queue_.receive();
            msg();
        }
    }
 public:
    MessagePump(): 
        done_(false)
    {
        thread_ = new thread ([=] { this->Run(); } ) );
    }

    ~MessagePump()
    {
        Send( [&]{ done = true; } );
        thread_->join();
    }

    void Send (function<void()> msg)
    {
        queue_.send(msg);
    }
};

Я преобразовал этот класс в C #, но у меня есть вопрос к коду в деструкторе. В соответствии с шаблоном IDisposable я должен предоставлять метод Dispose () только для освобождения управляемых и неуправляемых ресурсов.

Должен ли я поместить код деструктора C ++ в:

  1. Пользовательский метод CleanUp (), который клиент должен вызывать при выходе из приложения? Что если клиент забудет?
  2. Метод Dispose () для IDisposable, чтобы клиент также мог его вызывать? Но опять же, что, если клиент забудет?
  3. Внутри метода финализатора C #, чтобы он всегда выполнялся? Я читал, что если у вас нет неуправляемых ресурсов, вы не должны включать метод финализатора, потому что это снижает производительность.
  4. Nowhere? Просто игнорируйте пометку флага done_ и просто позвольте GC обработать его естественным образом, поскольку объект Thread является управляемым ресурсом? Будет ли поток принудительно прерван таким образом?

Я также обнаружил, что если я не помечаю поток рассылки сообщений, созданный внутри конструктора, как фоновый поток, мой объект MessagePump никогда не получает GC'а, и приложение просто зависает при выходе. В чем причина?

Ответы [ 2 ]

2 голосов
/ 16 сентября 2010

На высоком уровне я бы просто предложил использовать пул потоков .NET (System.Threading.ThreadPool) для постановки в очередь и выполнения нескольких рабочих элементов, поскольку это то, для чего он был разработан (предполагая, что рабочие элементы разрешено выполнять асинхронно). В частности, проверьте метод QueueUserWorkItem.

Чтобы ответить на ваши вопросы, хотя:

Должен ли я поместить код деструктора C ++ в:

Пользовательский метод CleanUp (), который клиент должен вызывать при выходе из приложения? Что если клиент забудет?

Метод Dispose () для IDisposable, чтобы клиент также мог его вызывать? Но опять же, что, если клиент забудет?

Всегда предпочитайте реализацию IDisposable по сравнению с пользовательскими CleanUp методами (в BCL некоторые классы Stream имеют метод Close, который на самом деле является просто псевдонимом для Dispose). Шаблон IDisposable - это способ выполнить детерминированную очистку с помощью C #. Клиент, забывший вызвать Dispose, всегда является проблемой, но это часто можно обнаружить с помощью инструментов статического анализа (например, FxCop).

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

Финализаторам не гарантируется выполнение (см. эту статью ), поэтому правильная программа не может предполагать, что они будут выполняться. Производительность не будет проблемой здесь. Я предполагаю, что у вас будет максимум пара MessagePump объектов, поэтому стоимость наличия финализатора незначительна.

Nowhere? Просто игнорируйте пометку флага done_ и просто позвольте GC обработать его естественным образом, поскольку объект Thread является управляемым ресурсом? Будет ли поток принудительно прерван таким образом?

Поток управляется CLR и будет должным образом очищен. Если поток возвращается из своей точки входа (Run здесь), он не будет прерван, он просто выйдет чисто. Этот код все еще должен куда-то идти, поэтому я бы обеспечил явную очистку через IDisposable.

Я также обнаружил, что если я не отмечу поток рассылки сообщений, созданный внутри конструктора, как фоновый поток, мой объект MessagePump никогда не получит GC, и приложение просто зависнет при выходе. В чем причина?

Приложение .NET работает до тех пор, пока не прекратятся все потоки переднего плана (не фоновые). Поэтому, если вы не пометите свой поток MessagePump как фоновый поток, он будет поддерживать ваше приложение во время его работы. Если какой-то объект все еще ссылается на ваш MessagePump, то MessagePump никогда не будет GC'едирован или завершен. Снова ссылаясь на статью выше, вы не можете предполагать, что финализатор когда-либо будет работать.

0 голосов
/ 13 июня 2011

Один шаблон, который может быть полезен, состоит в том, чтобы внешние пользователи насоса сообщений содержали сильные ссылки на объект флага «STILL IN USE», к которому сам насос имеет только слабую слабую ссылку (которая будет недействительной, как толькообъект "STILL IN USE" становится подходящим для завершения).Финализатор для этого объекта мог бы отправить сообщение обработчику сообщений, а обработчик сообщений мог бы проверить непрерывность его слабой ссылки;если он стал недействительным, насос сообщений мог бы затем отключиться.

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

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