Dispose race condition fix

This PR ensures that Dispose doesn't cause a `NullReferenceException` for outstanding operations.

I added a very complicated looking unit test (though in principle it's simple, it just looks nasty because THREADING). The test sets up three threads so we can get into the error situation.

The rules are: `Dispose` effectively waits for outstanding operations on `MemoizedRequests` to complete before it does it's thing. Likewise, any new requests must wait for `Dispose` to complete.

`Dispose is only called once. Any future calls exit immediately and do nothing.`

If an outstanding operation is waiting on a lock of `MemoizedRequests` and the cache is disposed, once that operation obtains the lock it will throw an `ObjectDisposedException`.

If an object is already disposed, operations throw an `ObjectDisposedException`.

_Note: I avoid the double check lock around `dispose` because it's an unnecessary optimization. The mainline case is the cache is not disposed. No need to optimized disposed access._