using System; using System.Diagnostics; using System.Threading; #nullable enable namespace DataLayer; /// Notifies clients that the object is being disposed. public interface INotifyDisposed : IDisposable { /// Event raised when the object is disposed. event EventHandler? ObjectDisposed; } /// Creates a single instance of at a time, blocking subsequent creations until the previous creations are disposed. public static class InstanceQueue where TDisposable : INotifyDisposed { /// Synchronization object for access to "/> private static Lock Locker { get; } = new(); /// Ticket holder for the last instance creator in line. private static Ticket? LastInLine { get; set; } /// Waits for all previously created instances of to be disposed, then creates and returns a new instance of using the provided factory. public static TDisposable WaitToCreateInstance(Func creator) { Ticket ticket; lock (Locker) { ticket = LastInLine = new Ticket(creator, LastInLine); } return ticket.Fulfill(); } /// A queue ticket for an instance creator. /// Factory to create a new instance of /// The ticket immediately in preceding this new ticket. This new ticket must wait for to signal its instance has been disposed before creating a new instance of private class Ticket(Func creator, Ticket? inFront) : IDisposable { /// Factory to create a new instance of private Func Creator { get; } = creator; /// Ticket immediately in front of this one. This instance must wait for to signal its instance has been disposed before creating a new instance of private Ticket? InFront { get; } = inFront; /// Wait handle to signal when this ticket's created instance is disposed private EventWaitHandle WaitHandle { get; } = new(false, EventResetMode.ManualReset); /// This ticket's created instance of private TDisposable? CreatedInstance { get; set; } /// Disposes of this ticket and every ticket queued in front of it. public void Dispose() { WaitHandle.Dispose(); InFront?.Dispose(); } /// /// Waits for the ticket's instance to be disposed, then creates and returns a new instance of using the factory. /// public TDisposable Fulfill() { #if DEBUG var sw = Stopwatch.StartNew(); #endif //Wait for the previous ticket's instance to be disposed, then dispose of the previous ticket. InFront?.WaitHandle.WaitOne(Timeout.Infinite); InFront?.Dispose(); #if DEBUG sw.Stop(); Debug.WriteLine($"Waited {sw.ElapsedMilliseconds}ms to create instance of {typeof(TDisposable).Name}"); #endif CreatedInstance = Creator(); CreatedInstance.ObjectDisposed += CreatedInstance_ObjectDisposed; return CreatedInstance; } private void CreatedInstance_ObjectDisposed(object? sender, EventArgs e) { Debug.WriteLine($"{typeof(TDisposable).Name} Disposed"); if (CreatedInstance is not null) { CreatedInstance.ObjectDisposed -= CreatedInstance_ObjectDisposed; CreatedInstance = default; } lock (Locker) { if (this == LastInLine) { //There are no ticket holders waiting after this one. //This ticket is fulfilled and will never be waited on. LastInLine = null; Dispose(); } else { //Signal the that this ticket has been fulfilled so that //the next ticket in line may proceed. WaitHandle.Set(); } } } } }