I have a library with an API much like this:
public class Library : IDisposable
{
public Library(Action callback);
public string Query();
public void Dispose();
}
After I have instantiated Library, at any time and on any thread it might invoke the callback I have passed to it. That callback needs to call Query to do useful work. The library will only stop calling my callback once disposed, but if a callback attempts to call Query after the main thread has called Dispose, bad stuff will happen.
I do want to allow the callbacks to run on multiple threads simultaneously. That’s okay. But we need to be sure that no callbacks can be running when we call Dispose. I thought a ReaderWriterLockSlim might be appropriate – you need the write-lock to call Dispose, and the callbacks need read-locks to call Query. The problem here is that ReaderWriterLockSlim is IDisposable, and I don’t think it will ever be safe to dispose it – I can never know that there is not a callback in flight that simply hasn’t gotten to the point of acquiring the read-lock yet.
What should I do? It looks like ReaderWriterLock isn’t IDisposable, but it’s own documentation says you should use ReaderWriterLockSlim instead. I could try to do something equivalent with just the “lock” keyword, but that sounds wasteful and easy to screw up.
PS – Feel free to say that the library API is not good if you think that’s the case. I would personally prefer that it guaranteed that Dispose would block until all callbacks had completed.
This sounds like something you can wrap with your own API which makes the guarantee from the final paragraph.
Essentially, each callback should atomically register that it’s running, and check whether it’s still okay to run – and then either quit immediately (equivalent to never being called) or do its stuff and deregister that it’s running.
Your
Disposemethod just needs to block until it finds a time when nothing’s running, atomically checking whether anything’s running and invalidating if not.I can imagine this being done reasonably simply using a simple lock, monitor,
Wait/Pulseapproach. Your API wrapper would wrap any callback it’s given inside another callback which does all this, so you only need to put the logic in one place.Do you see what I mean? I don’t have time to implement it for you right now, but I can elaborate on the ideas if you like.