0

In a discussion on the Dispose method in Jeffrey Richter's CLR via C#. The author states:

However, the design guidelines state that Dispose does not have to be thread-safe. The reason is because code should be calling Dispose only if the code knows for a fact that no other thread is using the object.

That sounds counter-intuitive to me. Surely if a class wraps a native or managed resource, once Dispose is called, it should make sure that no other caller is currently using the resource at the same time as it tries to release the resource. It seems to me that the wrapper, knowing how many callers are using the resource, has a much easier job of synchronizing the Dispose than all different callers that may not be aware of one another. There have been similar questions here and here but they haven't really have a definitive answer to this.

What's the justification behind this design guideline?

Community
  • 1
  • 1
Farhad Alizadeh Noori
  • 2,276
  • 17
  • 22
  • 1
    How would you see that? One thread using the object while it is disposed? Or two objects disposing the same object? Both don't make sense to me. – Patrick Hofman Nov 12 '14 at 13:51
  • 2
    Your assumption that a wrapper can easily know what code is holding a reference to it is entirely false. That kind of knowledge requires help from the client code, reference counting is the standard approach. But that just replaces one contract by another, one that can fail just as easily if the client code fumbles the requirements. Leaks are a standard reference counting bug, very hard to fix. – Hans Passant Nov 12 '14 at 14:41
  • @PatrickHofman If one thread uses an object while another thread is trying to dispose it, the dispose method could return an exception that this resource is currently being used. Or it could supply a call back to call when the resource is released. The other scenario of two objects disposing at the same time is easier to handle as the dispose method is supposed to allow multiple calls with no side effects. – Farhad Alizadeh Noori Nov 12 '14 at 14:48
  • @HansPassant If that is the case, what is the justification of suppling `SafeWaitHandles` ? These wrappers are there to make sure that the handles are disposed of safely. They do reference counting and provide special finalization capabilities. Why can't the `Dispose` method follow this pattern? – Farhad Alizadeh Noori Nov 12 '14 at 14:49
  • No, reference counting a SafeHandle is a built-in feature only for the pinvoke marshaller. Which is what makes it safe, the handle can't be destroyed while native code is using it. That is already ably done for disposable objects with the *using* statement. – Hans Passant Nov 12 '14 at 14:57
  • @HansPassant Although SafeHandle is a built-in feature, it is not only for pinvoke. We can actually inherit from SafeHandle see [here](http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.aspx). The point is, here we have a wrapper that is able to handle release of a resource even if there are more than one callers out there. My point was, why is this now different for the `Dispose` method that has the same responsibility with the difference of being called deterministically. – Farhad Alizadeh Noori Nov 12 '14 at 15:01
  • 1
    If you dispose an object while anther thread is using it you have a bug. No point complicating the design of an interface to make buggy programs slightly less likely to crash. – Voo Nov 12 '14 at 16:49
  • @Voo I don't think anyone has given a good argument for this. Not complicating the design of the interface would result the complication bubbling up to all callers. Then, you would probably end up centralizing the synchronization logic and have all callers use the same shared state. Why not move the shared state where I think it belongs? The object that actually handles the connection to the shared state. – Farhad Alizadeh Noori Nov 12 '14 at 17:31
  • You're assuming that an object can determine that it is currently "in use." That might hold true sometimes, but not in the general case. – Jim Mischel Nov 12 '14 at 18:17
  • @Farhad fit the vast, vast majority of programs you don't need that functionality at all and even if you do you'll need higher level synchronization anyhow. Don't make people pay for things that they won't need. – Voo Nov 12 '14 at 19:57
  • @JimMischel I think you can always design for the resource handler to know this. I think if you give me an example I'd be able to better understand. Could you think of an example in which there is no design to enforce the clients to subscribe themselves and hence the wrapper knowing about them? – Farhad Alizadeh Noori Nov 13 '14 at 13:16
  • @Voo Maybe we are thinking about different things. What I'm imagining right now is a class library developer, providing a resource to an application developer. The class library developer is handling a sensitive resource and should make sure that this resource is not to be released while others are using it and adding information to it. Let's say a caller not knowing another caller is writing information, decides that it doesn't need the resource anymore. Why can't the wrapper here enforce this? Why should we need another higher level synchronization? Why should sync go up rather than down? – Farhad Alizadeh Noori Nov 13 '14 at 13:20
  • But you can't design for the resource handler to know this. Take the simple case of two threads writing to the same `FileStream`. As long as those threads use a mutex of some kind, that's perfectly OK. But the underlying `FileStream` can't reliably determine when one thread is "done" using the resource. The responsibility for properly disposing the resource has to lie on the application--either the threads agree on who calls `Dispose`, or whatever controls those threads does it. But the `FileStream` itself can only do what it's told, which is dispose when told to. – Jim Mischel Nov 14 '14 at 15:22
  • @JimMischel Yes the handler wouldn't know when the callers are done with the resource unless they actually notify the handler which they do with Dispose. Don't forget what the original question was. The design guideline states that Dispose doesn't have to be thread-safe because client code should only dispose once sure no other client is using said resource. How should the client know this? The client should have access to a list of current clients using the resource. Now, my point was, why not do this design in the handler? The handler could supply a factory method with which each caller... – Farhad Alizadeh Noori Nov 14 '14 at 18:00
  • @JimMischel has to pass itself. Once each caller subscribes. Each caller then, without knowing anything about the other callers would try to dispose the resource. The handler knows that it should not release until all callers are done. It would do so when the last caller left requests a dispose. A design like that would allow this. – Farhad Alizadeh Noori Nov 14 '14 at 18:01
  • @Farhad: how the application should know this? By whatever medium it sees fit - I can think of several easy approaches and seriously most of the time if you share resources without clearly defined ownership rules you have a bad application design (you just get away with that much better than in c++). There's just no reason to make everyone pay for a feature that's rarely useful and should be discouraged anyhow. – Voo Nov 14 '14 at 22:03

1 Answers1

0

A key point which is often overlooked with Dispose is that its purpose is not to do anything to the object being disposed, but rather is to allow the object being disposed to notify outside entities that their services are no longer required. There are many situations in which an outside entity may be shared by many objects which are used by different threads. In many cases it will thus be important that the outside entity be capable of multiple notifications or requests from different threads simultaneously. If the outside entity uses a pool to manage resources, it may not need to worry about simultaneous actions being performed upon a particular pool item, but it must be prepared for the possibility that it might receive multiple simultaneous requests to allocate or release items from/to the same pool.

A second point is that certain kinds of communication libraries do not have any non-blocking methods and support exactly one multi-threaded scenario: an action which is blocking may be asynchronously aborted by any thread. Aborting an action in this fashion will leave the channel in an undefined state; the channel will not be usable again until such time as it is closed and re-opened. Because such an abort will leave the channel in a useless state, there is no reason to continue holding any resources associated with it after issuing the abort. Consequently, a common and useful pattern is to use Dispose itself to perform the abort. Such an abort must be run from a thread separate from the object's main thread [if the object's main thread is blocked, it can't run any user code to trigger the abort!] it must be thread-safe. Note that actual cleanup behavior might not necessarily be run on the thread that calls Dispose. If Dispose is called while the object is "running" code on its main thread (or such code is blocked within one of the object's methods) it may set a flag to force its main thread to do the cleanup; if it's called while the object's own thread is not executing its code, the object may let the thread which is calling Dispose become its "main thread", which could then clean it up; if its former "main thread" attempts to do something during cleanup, such action would fail immediately.

Although in most cases Dispose shouldn't be called on an object while it's still in use, there are some situations like the above where that would represent a necessary usage scenario. Yanking a resource out from under a consumer may be a rather crude way of forcing that consumer to shut down, but in many cases it may be safer than any available alternative. Additionally, even in cases where an object shouldn't be disposed but is anyway, a Dispose method should strive to limit the harm such disposal could cause. It's fine if calling Dispose on an object while it's still in use causes operations on the object to fail in unpredictable fashion. It's not so fine if calling Dispose on an one object which is still in use causes the next object created to be given resources which the first object is still using. Whether or not Dispose behaves in truly thread-safe fashion, it should ensure that harm caused by improper usage will be limited to the object being disposed.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • I see. Thanks for your answer. What do you think about this statement specifically: "code should be calling Dispose only if the code knows for a fact that no other thread is using the object." – Farhad Alizadeh Noori Nov 14 '14 at 18:21
  • @FarhadAlizadehNoori: I would rephrase it as "Code should be calling `Dispose` only if it knows that any thread which might be using the object *should cease doing immediately*, and that the contracts of any threads that might be using the object would require that they be prepared for it to become suddenly unusable [both conditions would automatically be satisfied if there were no other threads]". Yanking a resource out from under a thread is rude, but if a thread is blocked on I/O the only other alternatives, `Thread.Interrupt()` and `Thread.Abort()` are far worse. – supercat Nov 14 '14 at 19:08