使用 Dispose() 或终结器来清理托管线程?
假设我有一个 C++0x 中的消息泵类,如下所示(注意,SynchronizedQueue 是函数
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++ 析构函数代码放入:
- 应用程序退出时客户端需要调用的自定义 CleanUp() 方法吗?如果客户忘记了怎么办?
- IDisposable 的 Dispose() 方法以便客户端也可以调用它?但话又说回来,如果客户忘记了怎么办?
- 在 C# 终结器方法中,它总是会执行?我读到,如果您没有任何非托管资源,则不应包含终结器方法,因为它会损害性能。
- 无处?既然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:
- 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?
- 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.
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在较高级别上,我只是建议使用 .NET 线程池 (
System.Threading.ThreadPool
)用于排队和执行多个工作项,因为这就是它的设计目的(假设允许工作项异步执行)。具体来说,请查看QueueUserWorkItem
方法。不过,要回答你的问题:
始终更喜欢实现
IDisposable
而不是自定义CleanUp
方法(在 BCL 中,某些Stream
类具有一个Close
方法,该方法是实际上只是Dispose
的别名)。IDisposable
模式是使用 C# 进行确定性清理的方法。客户端忘记调用Dispose始终是一个问题,但这通常可以通过静态分析工具(例如FxCop)检测到。不保证终结器一定会执行(请参阅 这篇文章),因此正确的程序不能假设它们会执行。性能在这里不会成为问题。我猜您最多会有几个
MessagePump
对象,因此拥有终结器的成本微不足道。该线程由 CLR 管理,并且会被正确清理。如果线程从其入口点(这里是
Run
)返回,它不会被中止,它只会干净地退出。不过,这段代码仍然需要到达某个地方,因此我将通过IDisposable
提供显式清理。.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 theQueueUserWorkItem
method.To answer your questions, though:
Always prefer implementing
IDisposable
over customCleanUp
methods (in the BCL, someStream
classes have aClose
method that is really just an alias forDispose
). TheIDisposable
pattern is the way to do deterministic cleanup with C#. The client forgetting to callDispose
is always an issue, but this can often be detected by static analysis tools (e.g. FxCop).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.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 throughIDisposable
.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 yourMessagePump
, then theMessagePump
will never be GC'ed or finalized. Referencing the article above again, though, you can't assume that the finalizer will ever run.一种可能有用的模式是让消息泵的外部用户持有对“仍在使用”标志对象的强引用,泵本身仅持有一个弱引用(一旦对象的“仍在使用”,该引用就会失效)使用中”有资格最终确定)。该对象的终结器可能能够向消息泵发送消息,并且消息泵可以检查其弱引用的持续有效性;如果它变得无效,则消息泵可能会关闭。
请注意,消息泵的一个常见困难是操作消息泵的线程往往会保持大量对象处于活动状态,而这些对象除了该线程之外什么都没有使用。我们需要一个单独的对象,线程将避免对该对象保留强引用,以确保可以清理事物。
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.