使用Dispose()或终结器来清理托管线程?

时间:2010-09-16 03:56:10

标签: c# multithreading idisposable finalizer

假设我在C ++ 0x中有一个消息泵类,如下所示(注意,SynchronizedQueue是一个函数队列< void()>并且当你在队列上调用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. IDisposable的Dispose()方法,以便客户端也可以调用它?但是,再次,如果客户忘记了怎么办?
  3. 在C#终结器方法内部,它将始终执行?我读过如果您没有任何非托管资源,则不应包含终结器方法,因为它会影响性能。
  4. 无处?只是忽略标记done_标志并让GC自然处理它,因为Thread对象是一个托管资源?线程会以这种方式被强制中止吗?
  5. 我还发现,如果我没有将构造函数内部创建的消息泵线程标记为后台线程,则我的MessagePump对象永远不会得到GC,并且应用程序在退出时会挂起。这是什么原因?

2 个答案:

答案 0 :(得分:2)

在高层次上,我建议使用.NET线程池(System.Threading.ThreadPool)来排队和执行多个工作项,因为这是它的设计目的(假设允许执行工作项)异步)。具体来说,请查看QueueUserWorkItem方法。

回答你的问题:

  

我应该将C ++析构函数代码放入:

     

客户端在退出应用程序时需要调用的自定义CleanUp()方法?如果客户忘记了怎么办?

     

IDisposable的Dispose()方法,以便客户端也可以调用它?但同样,如果客户忘记了怎么办?

始终更喜欢在自定义IDisposable方法上实施CleanUp(在BCL中,某些Stream类具有Close方法,该方法实际上只是{{1}的别名})。 Dispose模式是使用C#进行确定性清理的方法。忘记拨打IDisposable的客户始终是个问题,但这通常可以通过静态分析工具(例如FxCop)来检测。

  

在C#终结器方法中,它总是会执行吗?我读过如果您没有任何非托管资源,则不应包含终结器方法,因为它会影响性能。

终结器不能保证执行(参见this文章),因此正确的程序不能假定它们将执行。性能不会成为问题。我猜你最多会有几个Dispose个对象,因此终结器的成本是非实质性的。

  

无处?只是忽略标记done_标志并让GC自然处理它,因为Thread对象是一个托管资源?线程会以这种方式被强制中止吗?

该线程由CLR管理,并将被正确清理。如果线程从其入口点(MessagePump这里)返回,它将不会被中止,它将只是干净地退出。这段代码仍然需要去某处,所以我会通过Run提供明确的清理。

  

我还发现,如果我没有将构造函数内部创建的消息泵线程标记为后台线程,则我的MessagePump对象永远不会得到GC,并且应用程序在退出时会挂起。这是什么原因?

.NET应用程序一直运行,直到所有前台(非后台)线程终止。因此,如果您不将IDisposable线程标记为后台线程,它将使您的应用程序在运行时保持活动状态。如果某个对象仍然引用了您的MessagePump,那么MessagePump将永远不会被GC或最终确定。但是,再次参考上面的文章,你不能假设终结器将会运行。

答案 1 :(得分:0)

一个可能有用的模式是让消息泵的外部用户持有对“STILL IN USE”标志对象的强引用,泵本身只持有一个弱弱引用(一旦有效,它将被无效)对象的“仍然使用”符合最终确定条件)。此对象的终结器可能能够向消息泵发送消息,并且消息泵可以检查其弱引用的持续有效性;如果它变得无效,则消息泵可以关闭。

请注意,消息泵的一个常见问题是操作它们的线程会使很多对象保持活动状态,除了该线程之外什么都没用。一个人需要一个单独的对象,线程将避免保留强引用,以确保可以清理事物。