4

假设我在 C++0x 中有一个消息泵类,如下所示(注意,SynchronizedQueue 是 function<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 对象是托管资源?这样线程会被强制中止吗?

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

4

2 回答 2

2

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

但是,要回答您的问题:

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

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

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

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

在 C# 终结器方法内部,它会始终执行吗?我读到如果您没有任何非托管资源,则不应包含终结器方法,因为它会损害性能。

终结器不能保证执行(请参阅本文),因此正确的程序不能假定它们会执行。性能在这里不会成为问题。我猜你MessagePump最多会有几个对象,所以拥有终结器的成本是微不足道的。

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

该线程由 CLR 管理,并将被适当地清理。如果线程从它的入口点(Run这里)返回,它不会被中止,它会干净地退出。这段代码仍然需要去某个地方,所以我会通过IDisposable.

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

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

于 2010-09-16T04:40:59.983 回答
0

一种可能有用的模式是让消息泵的外部用户持有对“STILL IN USE”标志对象的强引用,而泵本身只持有一个弱弱引用(一旦对象的“STILL IN USE” 有资格进行最终确定)。该对象的终结器可能能够向消息泵发送消息,并且消息泵可以检查其弱引用的持续有效性;如果它变得无效,则消息泵可以关闭。

请注意,消息泵的一个常见困难是操作它们的线程倾向于保持大量对象处于活动状态,这些对象除了该线程之外什么都没有使用。需要一个单独的对象,线程将避免对其保持强引用,以确保事情可以得到清理。

于 2011-06-12T20:36:39.690 回答