Runtime & concurrency
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using var gate = new SemaphoreSlim(1, 1);
var first = await Lease.TakeAsync(gate);
Task<Lease> pending = Lease.TakeAsync(gate).AsTask();
Console.WriteLine($"waiting:{!pending.IsCompleted}");
await first.DisposeAsync(); await first.DisposeAsync();
var second = await pending;
Console.WriteLine($"inside:{gate.CurrentCount}");
await second.DisposeAsync(); await second.DisposeAsync();
Console.WriteLine($"final:{gate.CurrentCount}");
sealed class Lease : IAsyncDisposable
{
private SemaphoreSlim? gate;
private Lease(SemaphoreSlim gate) { this.gate = gate; }
public static async ValueTask<Lease> TakeAsync(SemaphoreSlim gate) { await gate.WaitAsync().ConfigureAwait(false); return new Lease(gate); }
public ValueTask DisposeAsync() { Interlocked.Exchange(ref gate, null)?.Release(); return ValueTask.CompletedTask; }
}
Reveal answer and reasoning
The output is waiting:True, inside:0, and final:1. The first lease owns the sole permit, so the second acquisition is genuinely incomplete when waiting is printed. Disposing the first lease twice releases only once because its reference is detached atomically. The second acquisition then completes and owns the permit, making CurrentCount zero at the inside observation.
Disposing the second lease twice again contributes exactly one release, so the final count is one. A common wrong trace predicts two permits or SemaphoreFullException from duplicate disposal because it overlooks the null detachment. Another predicts inside one even though the second lease has already successfully acquired. This controlled sequence does not demonstrate FIFO among arbitrary waiters or a particular thread transition.
Its ownership conclusions come from the arranged pending operation and successful lease transfers, not from sleeps or scheduler timing.
What a strong answer covers
- The second acquisition waits while the first holds.
- Each lease releases at most once.
- The final shared count returns to one.
Common pitfall
Forgetting that successful second acquisition consumes the released permit before inside is printed.
Follow-up questions
- What should happen if acquisition is canceled before a lease exists?
- How would concurrent disposal of one lease be tested?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.