-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
Performing a simple loop & wait while asynchronously awaiting either a SemaphoreSlim event or a task (via Task.WhenAny) seems to cause an ongoing increase in the number of "SemaphoreSlim+TaskNode" instances on the heap when examined via the Managed Memory snapshot (Visual Studio Diagnostic Tools).
Sample code shows a fairly common example of a service that might be watching ~100 different resources, polling every 5 seconds for new data/files/events. It either waits for its configured polling interval (hardcoded here to 5 seconds) OR until the SemaphoreSlim gets released (an interrupt).
Reproduction Steps
internal class Program
{
private static readonly SemaphoreSlim _interrupter = new(0, 1);
private static readonly CancellationToken _cancellationToken = CancellationToken.None;
static async Task Main()
{
var procs = Enumerable.Range(1, 100).Select(i => Process()).ToList();
await Task.WhenAll(procs);
}
private static async Task Process()
{
while (true)
{
await Interruption(TimeSpan.FromMilliseconds(5000));
// look for new data here
}
}
private static async Task Interruption(TimeSpan delay)
{
var delayTask = Task.Delay(delay, _cancellationToken);
var interruptTask = _interrupter.WaitAsync(_cancellationToken);
// Wait until either the delay has expired or we got interrupted
await Task.WhenAny(interruptTask, delayTask);
}
}
Expected behavior
That the GC would collect these instances. In the code, it is not obvious/intuitive at all that this would result in a memory leak.
Actual behavior
After 60s: (comparing to first snapshot)

After 5 mins runtime: (comparing to first snapshot)

Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
No response

