Suppose I have this class:
public class StreamEntity : IPersistableEntity, IDisposable
{
public Stream Stream { get; set; }
public void Dispose()
{
if(Stream != null) Stream.Dispose();
}
}
Now let's say I want to store an instance of this class in some kind of cache, like MemoryCache.
After storing the entity in the cache, I proceed to dipose it.
After some time, a consumer tries to retrieve the same entity so I fetch it from the cache, but there is a little problem... The object is disposed, so I cannot read its Stream
property (Remember we disposed it after caching it?)
Now, one way to solve this, is to never dispose the cached entities. This seems logical since they should be kept alive in memory, but what happens after expiration?
Specifically, when MemoryCache
automatically removes an object that has expired, will it dispose it?
According to Will MemoryCache dispose IDisposable items when evicted?, MemoryCache
will not dispose its cached items.
Knowing this, How can I cache a disposable object? When should I dispose the entities if I'm not in control of their eviction?
You are correct that MemoryCache
does not call Dispose
, however you can tell it to call Dispose when evicting a item.
static void Main(string[] args)
{
var policy = new CacheItemPolicy
{
RemovedCallback = RemovedCallback,
SlidingExpiration = TimeSpan.FromMinutes(5)
};
Stream myStream = GetMyStream();
MemoryCache.Default.Add("myStream", myStream, policy);
}
private static void RemovedCallback(CacheEntryRemovedArguments arg)
{
if (arg.RemovedReason != CacheEntryRemovedReason.Removed)
{
var item = arg.CacheItem.Value as IDisposable;
if(item != null)
item.Dispose();
}
}
The above example creates a Stream
object and if it is unused for 5 minutes it will have Dispose()
called on it. If the stream is removed due to a Remove(
call removing the item or a Set(
call overwriting the item it will not have Dispose()
called on it.
The first thing to consider is whether it's a good idea to cache such an item at all. Many disposable objects hold onto relatively limited resources, and/or some which will time-out in some way. These do not cache well, and it's best just not to do so.
On the other hand, some disposable objects don't really need to be disposable, but they share a base class with many that do, or implement an interface that needs to allow for disposal at a particular point if it is done (IEnumerator<T>
) and so you could know that it's actually fine to not dispose it at all. In such a case you can cheerfully ignore the issue, but be careful of changes in implementation with later versions, unless the Dispose()
is explicitly documented as safe to ignore.
Yet other possibility is to cache something that allows for quicker construction of an object, which is the approach I'd recommend with Stream
: Don't cache Stream
objects at all, but rather cache the bytes that could be read from it. When calling code wants to read the stream first construct a new MemoryStream
with that byte array as the buffer. If the stream can be accessed from outside the assembly wrap that stream in another stream that enforces a read-only policy (if it is only accessible inside your own code you can skip that as an optimisation, by just being careful never to write to the stream). Then return that stream. The calling code can treat it like a stream obtained any other way (including calling Dispose()
when it's done) but you can still give the calling code those stream faster because of the caching.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With