Утечка памяти при использовании Powershell Remote Calls в C#

У меня есть служба Windows, которая делает много удаленных вызовов обмена, чтобы получить некоторую информацию о сервере. Я заметил, что с течением времени объем памяти, используемой службой, начинает расти, пока не будет выброшено исключение памяти. Я искал, и похоже, что есть известная утечка памяти в System.Management.Automation который не располагает всей памятью о Runspace создается при вызове метода close и / или dispose. Я просмотрел пост, в котором предлагается использовать CreateOutOfProcessRunspace из RunspaceFactory но не уверен, как его использовать.

Вот как можно воспроизвести проблему: (System.Management.Automation длл ссылается)

for (int i = 0; i < 1000; i++)
{
    var runspace = RunspaceFactory.CreateRunspace();
    runspace.Open();
    runspace.Close();
    runspace.Dispose();
}

Если вы запустите этот код, вы увидите, как увеличивается память. Из-за требований держать как можно более открытым соединение не является хорошим решением.

Знаете ли вы, как я могу решить эту проблему, даже используя CreateOutOfProcessRunspace метод RunspaceFactory или как правильно распорядиться памятью?

заранее спасибо

РЕДАКТИРОВАТЬ

Я использовал V3 и изменил создание пространства выполнения, чтобы использовать метод CreateRunspacePool, и похоже, утечка исчезла. Большое спасибо за Вашу помощь!

2 ответа

Решение

Я вижу проблему в PS v3.0, но не в PS v2.0. Вот код, который я использую, чтобы увидеть это (все примеры в PowerShell):

for() {
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()
    $runspace.Close()
    $p = Get-Process -Id $PID
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
}

Похоже, что дескрипторы и память просочились в v3.0 в коде выше.

Поскольку v2.0 не имеет этой проблемы, одним из возможных решений может быть запуск службы с использованием PS v2.0, т.е. PowerShell.exe -Version 2.0,

Если это невозможно, я могу придумать еще два обходных пути. Один из них - не создавать пространства выполнения напрямую, а использовать [powershell] вместо. Например, этот код не показывает утечку в v3.0:

for() {
    $ps = [powershell]::Create()
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}

Другим обходным решением, если это применимо, может быть использование[runspacefactory]::CreateRunspacePool(), Этот способ также не показывает утечку:

$rs = [runspacefactory]::CreateRunspacePool()
$rs.Open()
for() {
    $ps = [powershell]::Create()
    $ps.RunspacePool = $rs
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}
#$rs.Close() # just a reminder, it's not called here due to the infinite loop

Последний также работает намного быстрее, потому что пространство выполнения отчасти используется повторно.

Я также столкнулся с той же проблемой, когда я использовал https://www.nuget.org/package s/System.Management.Automation/. Но проблема была решена с помощью v3 System.Management.Automation и изменения кода для использования метода CreateOutOfProcessRunspace

Вот код

            using (PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(4, 0), null, null, false))
        {

            using (var runspace = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(new string[0]), instance))
            {
                runspace.Open();

                using (PowerShell powerShellInstance = PowerShell.Create())
                {
                    powerShellInstance.Runspace = runspace;

                    var filePath = GetScriptFullName(powerShellScriptType);
                    powerShellInstance.Commands.AddScript(File.ReadAllText(filePath));

                    var includeScript = GetIncludeScript();
                    powerShellInstance.AddParameters(new List<string>
                {
                    userName,
                    plainPassword,
                    includeScript
                });
                    Collection<PSObject> psOutput = powerShellInstance.Invoke();

                    // check the other output streams (for example, the error stream)
                    if (powerShellInstance.Streams.Error.Count > 0)
                    {
                        // error records were written to the error stream.
                        // do something with the items found.
                        var exceptions = "";
                        foreach (var error in powerShellInstance.Streams.Error)
                        {
                            exceptions += error.Exception + "\n";
                        }

                        throw new InvalidPowerShellStateException(exceptions);

                    }
                    return psOutput;
                }
            }
        }
Другие вопросы по тегам