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

发布于 2024-09-19 18:57:12 字数 1171 浏览 3 评论 0原文

假设我有一个 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. IDisposable 的 Dispose() 方法以便客户端也可以调用它?但话又说回来,如果客户忘记了怎么办?
  3. 在 C# 终结器方法中,它总是会执行?我读到,如果您没有任何非托管资源,则不应包含终结器方法,因为它会损害性能。
  4. 无处?既然Thread对象是托管资源,就忽略标记done_标志并让GC自然处理它?这样线程会被强制中止吗?

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

Suppose I have a message pump class in C++0x like the following (note, SynchronizedQueue is a queue of function<void()> and when you call receive() on the queue and it is empty, it blocks the calling thread until there is an item to return):

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);
    }
};

I have converted this class into C#, but I have a question for the code in the destructor. According to the IDisposable pattern, I should only provide a Dispose() method in order to free managed and unmanaged resources.

Should I put the C++ destructor code into:

  1. A custom CleanUp() method that the client needs to call when application is exiting? What if the client forgets?
  2. A Dispose() method of IDisposable so that the client can also call it? But again, what if the client forgets?
  3. Inside the C# finalizer method so it will always execute? I read that if you do not have any unmanaged resources, you shouldn't include a finalizer method because it hurts performance.
  4. Nowhere? Just ignore marking the done_ flag and just let GC handle it naturally since the Thread object is a managed resource? Will the thread be forcibly aborted in this way?

I have also found out that if I don't mark the message pump thread created inside the constructor as a background thread, my MessagePump object never gets GC'ed and the application just hangs when it exits. What's the reason for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

琉璃繁缕 2024-09-26 18:57:13

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

不过,要回答你的问题:

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

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

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

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

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

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

无处可去?既然Thread对象是托管资源,就忽略标记done_标志并让GC自然处理它?这样线程会被强制中止吗?

该线程由 CLR 管理,并且会被正确清理。如果线程从其入口点(这里是Run)返回,它不会被中止,它只会干净地退出。不过,这段代码仍然需要到达某个地方,因此我将通过 IDisposable 提供显式清理。

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

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

At a high level, I would just suggest using the .NET thread pool (System.Threading.ThreadPool) for queueing and executing multiple work items, since that's what it was designed for (assuming the work items are allowed to be executed asynchronously). Specifically, check out the QueueUserWorkItem method.

To answer your questions, though:

Should I put the C++ destructor code into:

A custom CleanUp() method that the client needs to call when application is exiting? What if the client forgets?

A Dispose() method of IDisposable so that the client can also call it? But again, what if the client forgets?

Always prefer implementing IDisposable over custom CleanUp methods (in the BCL, some Stream classes have a Close method that is really just an alias for Dispose). The IDisposable pattern is the way to do deterministic cleanup with C#. The client forgetting to call Dispose is always an issue, but this can often be detected by static analysis tools (e.g. FxCop).

Inside the C# finalizer method so it will always execute? I read that if you do not have any unmanaged resources, you shouldn't include a finalizer method because it hurts performance.

Finalizers are not guaranteed to execute (see this article), so a correct program cannot assume that they will execute. Performance won't be an issue here. I'm guessing you'll have a couple of MessagePump objects at most, so the cost of having a finalizer is insubstantial.

Nowhere? Just ignore marking the done_ flag and just let GC handle it naturally since the Thread object is a managed resource? Will the thread be forcibly aborted in this way?

The thread is managed by the CLR and will be properly cleaned-up. If the thread returns from its entry point (Run here), it won't be aborted, it will just exit cleanly. This code still needs to go somewhere though, so I would provide explicit cleanup through IDisposable.

I have also found out that if I don't mark the message pump thread created inside the constructor as a background thread, my MessagePump object never gets GC'ed and the application just hangs when it exits. What's the reason for this?

A .NET application runs until all foreground (non-background) threads terminate. So if you don't mark your MessagePump thread as a background thread, it will keep your application alive while it runs. If some object still references your MessagePump, then the MessagePump will never be GC'ed or finalized. Referencing the article above again, though, you can't assume that the finalizer will ever run.

空名 2024-09-26 18:57:13

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

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

One pattern that may be helpful is to have outside users of the message pump hold strong references to a "STILL IN USE" flag object to which the pump itself only holds a weak weak reference (which will be invalidated as soon as the object's "STILL IN USE" becomes eligible for finalization). The finalizer for this object might be able to send the message pump a message, and the message pump could check the continued validity of its weak reference; if it has become invalid, the message pump could then shut down.

Note that one common difficulty with message pumps is that the thread that operates them will tend to keep alive a lot of objects which are used by nothing but that thread. One needs a separate object, to which the thread will avoid keeping a strong reference, to ensure that things can get cleaned up.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文