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();
}
}
}
}
}