18

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?

Community
  • 1
  • 1
zneak
  • 134,922
  • 42
  • 253
  • 328
  • See [this post](http://stackoverflow.com/questions/6005865/prevent-net-garbage-collection-for-short-period-of-time). It comes with all sorts of warnings. – Scott Hannen Aug 02 '16 at 20:49
  • @ScottHannen, I stumbled upon it too (I linked to it in the paragraph about GC.TryStartNoGCRegion). I don't think that it shows anything about excluding threads from garbage collection. – zneak Aug 02 '16 at 20:51
  • 7
    The CLR cannot suspend a thread that is busy running native code. Nor does it have to, no object roots can change while it is running such code. So no need to "ask", just make sure your time-critical code is native code and it doesn't return back to a managed method while doing its stuff and nothing can go wrong. – Hans Passant Aug 02 '16 at 20:51
  • @HansPassant, is that documented anywhere? If it is, that would be a valid answer. – zneak Aug 02 '16 at 20:54
  • 2
    @HansPassant, I found that "[Fundamentals of Garbage Collection](https://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx#workstation_and_server_garbage_collection)" documents that behavior for workstation-style garbage collection. – zneak Aug 02 '16 at 21:01
  • Just a thought: maybe you could use the C++ part as-is for the audio stuff and use unsafe C# and P/Invoke to call the C++ stuff (performance of P/Invoke got a bad rap because of marshalling, but if you're passing unsafe C# pointers instead of letting the runtime marshall stuff for you, the only performance overhead will be a 30-100 cycle context switch between managed and unmanaged, which is the same as C++/CLI). – Robert Fraser Aug 22 '16 at 20:49
  • Is there an option to separate both into two different Process and use Interprocess communication to communicate between them? – Akash Kava Jan 21 '17 at 16:54

2 Answers2

3

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.
xer21
  • 304
  • 1
  • 11
  • 1
    I appreciate the research effort, but I'd like to point out that there *is* documentation evidence that the workstation garbage collector will not interrupt a thread that is running native code. See [Fundamentals of Garbage Collection](https://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx#workstation_and_server_garbage_collection), scrolling down to "Comparing workstation and server garbage collection". – zneak Aug 20 '16 at 04:14
  • Ah yes my answer is specific to threads running managed code. For the before and after your .NET application is calling your native code. In other words you answered your own question then, "How to tell .NET not to suspend threads executing native code during GC? - Configure your application to use workstation GC mode, See: Elementhttps://msdn.microsoft.com/en-us/library/ms229357%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396" – xer21 Aug 22 '16 at 19:25
  • @zneak Just wanted to follow up and see if my updated answer was of any help, thanks. – xer21 Sep 09 '16 at 20:04
-3

Didn't check it but, I would inherit Thread, or Task whatever you use like:

public class SuppressedThread : Thread
{            
    protected override void Finalize()
    {
         try
         {
          // kill what you want
         }
         finally
         {
          base.SupressFinalizeFinalize();
         }
    }
}

public class SuppressedThread : Thread, IDisposable
{    
    protected override void Dispose()
    {
         try
         {
          // kill what you want
         }
         finally
         {
          GC.SupressFinalize();
         }
    }
}

then you can use it normally

var newThread = new SuppressedThread(DoWork);
newThread.Start(42);

What do you think?

adi ben
  • 491
  • 5
  • 15
  • 4
    This has many problems. 1) `Thread` is sealed; you can't inherit from it. 2) Even if you could inherit from it, this would only prevent GC of the thread itself, not the objects created on the thread. 3) the `Finalize` method cannot be overridden in C#. Instead the finalizer syntax is used, like `~Foo()`. – vcsjones Aug 29 '16 at 13:07