Use Dispose() or finalizer to clean up managed threads?
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?
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.
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.
精彩评论