Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I tell the .NET GC to leave some threads alone?

I'm investigating the possibility of rewriting a relatively small service from C++ to C#. The service has two main functions:

  1. Execute HTTP requests once in a while. They involve several high-level tasks like JSON encoding/decoding, base64 encoding/decoding, and HTTP requests themselves, for which C++ isn't awesome;
  2. Perform a number of real-time, audio-related tasks that have hard deadlines, for which C# isn't awesome.

The real-time tasks are handled by a separate library that does its own threading stuff and barely interacts with the rest of the service at all. The rest of the service feeds it a little bit of data, obtained from the HTTP requests, every 5 minutes or so.

The thing is, since the real-time part has hard deadlines, I can't really tolerate GC pauses on the library's threads. On my own code's side, there should be plenty of time for the GC to run between Web requests, but I can't tolerate that it kicks in while I'm trying to feed data to the library either.

I found that I can create a critical section in which the garbage collector won't start using GC.TryStartNoGCRegion(), which solves half of the problem.

However, I still don't know if there is a way to tell the .NET GC to leave alone specific threads that don't run managed code. Is that possible?

like image 508
zneak Avatar asked Aug 02 '16 18:08

zneak


1 Answers

As you have pointed out, when an application is configured to run in Workstation GC Mode, the garbage collector wont suspend threads executing native code. In that case anytime your service receives a request I would try something like this...

    private bool _running = true;
    private int _workCounter = 0;
    private AutoResetEvent _workFlag = new AutoResetEvent(false);
    private void RunNoGCNativeCode(params object[] args)
    {
        // Increase the work counter to determine how many requests are being processed
        if (Interlocked.Increment(ref _workCounter) == 1)
        {
            // Try to start a No GC Region
            GC.TryStartNoGCRegion(1 * 1024 * 1024 * 1024, true);
        }
        // TODO: Prep data and execute your native code
        // TODO: Process response
        // TODO: Dispose of anything that is no longer in use and null objects as needed
        if (Interlocked.Decrement(ref _workCounter) == 0 && GCSettings.LatencyMode == GCLatencyMode.NoGCRegion)
        {
            GC.EndNoGCRegion();
        }
        // Notify Manual Collection thread work has been completed
        _workFlag.Set();
    }

On a different thread...

    private void WaitForNoWorkThenGC()
    {
        // Continue running thread while in use
        while (_running)
        {
            // Wait for some work to be complete
            _workFlag.WaitOne();
            // If there is no work being processed call GC.Collect() 
            if (_workCounter == 0)
            {
                GC.Collect();
            }
        }
    } 

This should help you control when GC occurs to minimize impact on your application

As for managed code, I have not found any indication that .NET Garbage Collection can "leave alone" specific threads executing managed code. All current modes(Server, Workstation, Concurrent, etc) of garbage collection will suspend ALL threads executing managed code when it chooses to. Do note that some GC modes can have a shorter pause in threads which might also help. I've tried using TryStartNoGCRegion() in a real-time c# application also and it ignores my request always because the amount of memory I use, I could not find a way to specify only collect after X memory limit is reached either.

However there are two potential solutions you can look into.

  1. Using Garbage Collection Notifications to monitor when the GC is approaching a full garbage collection. Potentially if you free up some memory manually before it ever reaches the collection point you can avoid Garbage Collection.
  2. Run the different type of requests in separate application processes? or pass them to separate application processes as you receive them. This way each application doesn't share context's and garbage collection will be handled separately.
like image 150
xer21 Avatar answered Oct 07 '22 21:10

xer21