Search code examples
c#garbage-collectionresourcesbackgroundworkerasp.net-core-hosted-services

Background service/worker don't garbage collect


I need to have an IHostedService, or "Worker Service", but I've come across an interesting conundrum, and that is that the Garbage Collector don't run at the "appropriate" times. Rather it just don't collect any resources, resulting in the application just increasing in memory use, (during debug, there's no GC happening at all).

I have done every thinkable thing, including having all my tasks return some value, and using using-statements where possible. Also abstracting a few layers with Dependency Injection, (to make sure there is multiple places that should communicate to the runtime that it's appropriate to run garbage collection.

My only "solution", which isn't a solution, is to manually run GC.Collect();

namespace Worker
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation($"Worker running at: {DateTime.Now}");

                var files = Directory.EnumerateFiles(@"C:\\repos\");

                var date = files.Select(f => File.ReadAllBytesAsync(f));

                GC.Collect();
            }
        }
    }
}

The result is that my code, without manually triggering GC, will bloat to max out the available memory, however, with the manual GC this can run for hours without noticeable changes in resources usage


Solution Using the settings mention in this article: https://dotnet.github.io/orleans/1.5/Documentation/Deployment-and-Operations/Configuration-Guide/Configuring-.NET-Garbage-Collection.html (deprecated)

Updated link: https://learn.microsoft.com/en-us/dotnet/core/run-time-config/garbage-collector


Solution

  • This is an expected behavior of server GC. It is tuned to make the best use of the available memory, and therefore won't collect unless it is really needed. Is that really issue? The OS is capable of paging memory in and out as needed, so if you're running in 64 bits it shouldn't have an impact in most scenarios. If that's still a concern, there are a few alternatives:

    • The easiest solution is to switch to workstation GC, which is way more aggressive and automatically collects memory at regular intervals. Setting the gcServer setting to false will activates workstation GC
    • You can play with the number of heaps allocated by the GC. If the CPU has a large number of cores, this can have a noticeable impact. This can be done using the GCHeapCount setting. See this article from Maoni Stephens for more information
    • You can create a Windows job to limit the memory available to the process. However this is much more complicated as, to the best of my knowledge, there is no built-in tool in Windows to do that

    .NET Core 3.0 will bring new settings to tweak the GC for low-memory scenarios, but it won't be released before the second semester.