timcassell / ProtoPromise

Robust and efficient library for management of asynchronous operations in C#/.Net.
MIT License
151 stars 13 forks source link

Add `Promise.GetRetainer()` API #423

Closed timcassell closed 4 months ago

timcassell commented 6 months ago

Currently, Promise(<T>).Preserve() returns the same type as the original promise Promise(<T>). It uses .Forget() to return it to the pool, which makes it unsafe to return it directly in public APIs, making .Duplicate() calls necessary.

This pattern is both inefficient (need to duplicate the promise) and is not so intuitive (it's not easy to tell at a glance whether a promise is preserved, .Forget() has a dual purpose which is not good API design, and needing to use .Duplicate() is non-obvious).

Both of these issues can be solved by introducing a new Promise(<T>).GetRetainer() API (similar to CancelationToken.GetRetainer()). This is to avoid a breaking change by changing the semantics of .Preserve(). GetRetainer() returns a new type: Promise.Retainer or Promise<T>.Retainer. These new types will implement IDisposable to return it to the pool (rather than using .Forget()). This makes it much more obvious that they are retainers and not just regular promises, and need to be cleaned up via the usual dispose pattern.

These types expose a GetPromise() method that returns a new promise that adopts the original promise's state. GetPromise() can be called multiple times until the retainer is disposed, so that it can be awaited multiple times (each promise returned from GetPromise() can be awaited only once). GetPromise() is functionally equivalent to the current Duplicate(), but is forced to be able to await it (Duplicate() is not forced), and can be optimized by simply incrementing a counter on the backing object instead of allocating a new object. This makes it safe to return in public APIs, and eliminates the need for duplicates.