Я обнаружил огромную утечку памяти в проекте, которая появляется после выполнения команды PS. Здесь я создаю пример, который представляет мою проблему:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public class TestCase
{
public void Test()
{
GetCommandsInfo(GetModules());
}
private void GetCommandsInfo(string[] modules)
{
InitialSessionState initialState = InitialSessionState.CreateDefault();
initialState.ImportPSModule(modules);
using (Runspace runspace = RunspaceFactory.CreateRunspace(initialState))
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline())
{
string script = "Get-Command -ListImported -CommandType Cmdlet,Alias";
pipeline.Commands.AddScript(script);
IEnumerable psObjects = pipeline.Invoke();
if (psObjects == null)
{
Debug.Fail("Pipeline.Invoke failed!");
throw new InvalidOperationException();
}
}
runspace.Close();
}
}
private static string[] GetModules()
{
string[] result;
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
using (Runspace runspace = RunspaceFactory.CreateRunspace(rsConfig))
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline())
{
pipeline.Commands.AddScript("Get-Module -ListAvailable");
IEnumerable psObjects = pipeline.Invoke();
if (psObjects == null)
{
Debug.Fail("Pipeline.Invoke failed!");
throw new InvalidOperationException();
}
result = GetModules(psObjects);
}
runspace.Close();
}
return result;
}
private static string[] GetModules(IEnumerable psObjects)
{
if (psObjects == null)
{
Debug.Fail("Get-Module: collection of PS Objects is null!");
throw new ArgumentNullException("psObjects");
}
List<string> result = new List<string>();
foreach (PSObject psObject in psObjects)
{
if (psObject == null || psObject.ImmediateBaseObject == null)
{
Debug.Fail("Get-Module: an item is null!");
continue;
}
PSModuleInfo moduleInfo = psObject.ImmediateBaseObject as PSModuleInfo;
if (moduleInfo == null)
{
Debug.Fail("Get-Module: an item is not PSModuleInfo!");
}
result.Add(moduleInfo.Name);
}
return result.ToArray();
}
}
После исследования использования памяти я обнаружил, что большая часть памяти занята внутренними структурами из System.Management.Automation. Поэтому я начал искать способ освобождения ресурсов и обнаружил, что такое же поведение можно воспроизвести в PS:
Get-Module -ListAvailable | ForEach { Import-Module $_.Name }
Get-Command -ListImported -CommandType Cmdlet,Alias
Я уже пытался инициировать сборку мусора и удалить все новые переменные, которые появляются после выполнения скрипта,Я даже пытался запустить новый Runspace в PowerShell ISE для запуска сценария, но ресурсы все еще не высвобождались.
Во время моего исследования я обнаружил, что этот код все еще потребляет кучу памяти:
public static void TestLeak()
{
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline())
{
StringBuilder builder = new StringBuilder();
builder.Append(@"for ($i = 0; $i -lt 1000; $i++)
{
Get-Process *
}");
pipeline.Commands.AddScript(builder.ToString());
Collection<PSObject> psObjects = pipeline.Invoke();
Console.WriteLine("Test count: " + psObjects.Count);
}
runspace.Close();
}
}
Но если мы изменим это:
Get-Process *
на это:
$res = Get-Process *
, тогда память не протечет. Переменная выходит из области видимости, а затем освобождается сборщиком мусора. Но в первом случае похоже, что PowerShell хранит результаты где-то во внутренних структурах. Я пытался добавить переменную в свой сценарий для хранения результатов командлета, но ничего не изменилось, так как моя переменная никогда не выходит из области видимости!
Я застрял и не могу найти решения для этой проблемы. Может быть, кто-то сталкивался с такой проблемой и может помочь мне с этим.