Утечка памяти с помощью удаленных вызовов Powershell в C#

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

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

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

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

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

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

редактировать

я использовал V3 и изменил создание runspace, чтобы использовать метод 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 в приведенном выше коде.

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

если это невозможно, я могу придумать еще два обходных пути. Одного из них нет чтобы создать runspaces напрямую но используйте . Например, это код не показывает утечку в 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

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


Я также столкнулся с той же проблемой, когда я использовал v1 системы.Управление.Автоматизация. Но вопрос был решен с v3 системы.Управление.Автоматизация и изменение кода для использования 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;
                }
            }
        }