运行 c# 脚本时处理泄漏

Handle leak when running c# scripts

提问人:filtrow 提问时间:11/16/2023 最后编辑:dropoutcoderfiltrow 更新时间:11/16/2023 访问量:68

问:

我有一个 .NET 应用程序,用户还可以在其中输入一些 c# 脚本。该脚本在应用程序中重复执行,当发生这种情况时,我看到句柄泄漏。

我使用来自 Microsoft.CodeAnalysis.CSharp.Scripting 的静态类 CSharpScript。 我尝试了一个非常简单的控制台应用程序,但问题仍然存在。 这是我使用的代码:

using Microsoft.CodeAnalysis.CSharp.Scripting;

while (true)
{
    var script = CSharpScript.Create("bool test = true;");
    script.Compile();
    using (var state = script.RunAsync())
    {
        var result = state.Result;
    }
}

以及:


using Microsoft.CodeAnalysis.CSharp.Scripting;

while (true)
{
    using (var state = CSharpScript.RunAsync("bool test = true;"))
    {
        var result = state.Result;
    }
}

我还尝试在每次迭代中调用垃圾回收器,但我得到了相同的结果。 在这两个示例中,句柄都飞向天空: 进程监视器中的句柄

C# .NET 内存 句柄 泄漏

评论

3赞 Selvin 11/16/2023
为什么你没有等待任务?
0赞 kipy 11/16/2023
您正在无限循环中编译和运行脚本,没有任何延迟,也无需等待任务完成。这无济于事。
0赞 filtrow 11/16/2023
@Selvin 你说得对,我只是错过了一行代码。编辑。
0赞 Artem Razin 11/16/2023
使用像 Deleaker 这样的分析器进行分析。至少,您应该能够识别泄漏手柄的类型。

答:

0赞 filtrow 11/29/2023 #1

我设法找到了解决问题的方法。经过一番搜索,我发现 EvaluateAsync 在内存中生成一个新程序集,并且无法删除。

我必须使用CSharpCompilation类而不是CSharpScript才能获得相同的结果,而不会发生内存泄漏!这有点棘手,但它效果很好。这里有一个小例子:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System.Reflection;

namespace MyNamespace
{
    public class MyData
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

class Program
{
    static void Main(string[] args)
    {
        MethodInfo method;
        object instance;

        string code = @"
            using System;

            public class DynamicClass 
            {
                public string DynamicMethod(MyNamespace.MyData data) 
                {
                    return ""Name: ""+ data.Name + "" Age: "" + data.Age;
                }
            }
        ";

        SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);

        string assemblyName = Path.GetRandomFileName();
        MetadataReference[] references = {
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            MetadataReference.CreateFromFile(typeof(MyNamespace.MyData).Assembly.Location) // Aggiunta del riferimento all'assembly contenente MyData
        };

        CSharpCompilation compilation = CSharpCompilation.Create(
            assemblyName,
            syntaxTrees: new[] { syntaxTree },
            references: references,
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
        using (var ms = new MemoryStream())
        {
            EmitResult result = compilation.Emit(ms);
            if (!result.Success)
            {
                foreach (Diagnostic diagnostic in result.Diagnostics)
                {
                    Console.WriteLine(diagnostic);
                }
                return;
            }
            else
            {
                ms.Seek(0, SeekOrigin.Begin);
                Assembly assembly = Assembly.Load(ms.ToArray());

                Type dynamicType = assembly.GetType("DynamicClass");
                instance = Activator.CreateInstance(dynamicType);


                method = dynamicType.GetMethod("DynamicMethod");
            }
        }

        while (true)
        {
            if (method is not null)
            {
                var data = new MyNamespace.MyData { Name = "John", Age = 30 };
                var outRes = method.Invoke(instance, new object[] { data });
                Console.WriteLine(outRes);
            }
        }
    }
}