Могу ли я сказать, что .NET GC оставляет одни потоки?

Я изучаю возможность перезаписи относительно небольшой службы с С++ на С#. Услуга имеет две основные функции:

  • Выполнять HTTP-запросы раз в то время. Они включают в себя несколько высокоуровневых задач, таких как кодирование/декодирование JSON, кодирование/декодирование base64 и сами запросы HTTP, для которых С++ не является удивительным;
  • Выполняйте ряд задач, связанных с аудио, связанных с реальным временем, которые имеют жесткие сроки, для которых С# не является удивительным.

Задачи в реальном времени обрабатываются отдельной библиотекой, которая выполняет собственные потоковые работы и практически не взаимодействует с остальной частью службы. Остальная часть сервиса подает ему немного данных, полученных из HTTP-запросов, каждые 5 минут или около того.

Дело в том, что, поскольку часть в реальном времени имеет жесткие сроки, я не могу терпеть GC-паузы в потоках библиотеки. На моей собственной стороне кода должно быть достаточно времени для того, чтобы GC работал между веб-запросами, но я не могу терпеть, что он срабатывает, пока я пытаюсь передать данные в библиотеку.

I found, что я могу создать критический раздел, в котором сборщик мусора не начнет использовать GC.TryStartNoGCRegion(), который разрешает половину проблемы.

Тем не менее, я до сих пор не знаю, есть ли способ сообщить .NET GC оставить только отдельные потоки, которые не запускают управляемый код. Возможно ли это?

Ответы

Ответ 1

Как вы указали, когда приложение настроено для работы в режиме GC рабочей станции, сборщик мусора не будет приостанавливать потоки, выполняющие собственный код. В этом случае в любое время, когда ваша служба получает запрос, я бы попробовал что-то вроде этого...

    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();
    }

В другой теме...

    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();
            }
        }
    } 

Это поможет вам контролировать, когда происходит GC, чтобы минимизировать влияние на ваше приложение.

Что касается управляемого кода, я не нашел никаких указаний на то, что .NET Garbage Collection может "оставить в покое" определенные потоки, выполняющие управляемый код. Все текущие режимы (Server, Workstation, Concurrent и т.д.) Сборки мусора приостанавливают выполнение всех потоков, выполняющих управляемый код при его выборе. Обратите внимание, что некоторые режимы GC могут иметь более короткую паузу в потоках, что также может помочь. Я попытался использовать TryStartNoGCRegion() в приложении С# в реальном времени также, и он игнорирует мой запрос всегда, потому что объем памяти, который я использую, я не мог найти способ указать только собирать после достижения предела Х-памяти.

Однако есть два возможных решения, которые вы можете изучить.

  • Используя Уведомления о сборе мусора, чтобы отслеживать, когда GC приближается к полной сборке мусора. Потенциально, если вы освободите некоторую память вручную, прежде чем она достигнет точки сбора, вы можете избежать сбора мусора.
  • Запускать разные типы запросов в отдельных процессах приложения? или передать их отдельным процессам приложений по мере их получения. Таким образом, каждое приложение не использует контекст, и сбор мусора будет обрабатываться отдельно.

Ответ 2

Не проверял, но, Я бы наследовал Thread, или Task, что вы используете как:

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();
         }
    }
}

то вы можете использовать его обычно

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

Как вы думаете?