C# 连锁反应:OutOfMemoryException

C# 连锁反应:OutOfMemoryException,c#,scripting,roslyn,.net-4.6,C#,Scripting,Roslyn,.net 4.6,我一直在努力了解Roslyn,看看它是否适合我的需要 在一个非常简单的项目中,我试图创建一个简单的“涟漪效应”,即每次迭代都会导致加载一个新程序集,最终在500次迭代后崩溃(OutOfMemoryException) 有没有办法在不引起爆炸的情况下做到这一点 class Program { static void Main(string[] args) { string code = @" IEnumerable<double> com

我一直在努力了解Roslyn,看看它是否适合我的需要

在一个非常简单的项目中,我试图创建一个简单的“涟漪效应”,即每次迭代都会导致加载一个新程序集,最终在500次迭代后崩溃(OutOfMemoryException)

有没有办法在不引起爆炸的情况下做到这一点

class Program
{
    static void Main(string[] args)
    {
        string code = @"
        IEnumerable<double> combined = A.Concat(B);
        return combined.Average();                    
        ";

        Globals<double> globals = new Globals<double>()
        {
            A = new double[] { 1, 2, 3, 4, 5 },
            B = new double[] { 1, 2, 3, 4, 5 },
        };

        ScriptOptions options = ScriptOptions.Default;            
        Assembly systemCore = typeof(Enumerable).Assembly;
        options = options.AddReferences(systemCore);
        options = options.AddImports("System");
        options = options.AddImports("System.Collections.Generic");
        options = options.AddImports("System.Linq");

        var ra = CSharpScript.RunAsync(code, options, globals).Result;

        for (int i = 0; i < 1000; i++)
        {
            ra = ra.ContinueWithAsync(code).Result;
        }            
    }
}

public class Globals<T>
{
    public IEnumerable<T> A;
    public IEnumerable<T> B;
}
类程序
{
静态void Main(字符串[]参数)
{
字符串代码=@“
IEnumerable合并=A.Concat(B);
返回组合的.Average();
";
Globals Globals=新的Globals()
{
A=新的双[]{1,2,3,4,5},
B=新的双[]{1,2,3,4,5},
};
ScriptOptions=ScriptOptions.Default;
组件systemCore=类型(可枚举)。组件;
options=options.AddReferences(systemCore);
选项=选项。添加项(“系统”);
options=options.AddImports(“System.Collections.Generic”);
options=options.AddImports(“System.Linq”);
var ra=CSharpScript.RunAsync(代码、选项、全局变量);
对于(int i=0;i<1000;i++)
{
ra=ra.ContinueWithAsync(代码).Result;
}            
}
}
公共类全局
{
公共可数A;
公共可数B;
}

每次使用CSharpScript.Run或Evaluate方法时,您实际上正在加载一个新脚本(一个.dll),该脚本恰好相当大。为了避免这种情况,您需要缓存正在执行的脚本:

_script = CSharpScript.Create<TR>(code, opts, typeof(Globals<T>)); // Other options may be needed here
\u script=CSharpScript.Create(code,opts,typeof(Globals));//此处可能需要其他选项
缓存了_脚本后,您现在可以通过以下方式执行它:

_script.RunAsync(new Globals<T> {A = a, B = b}); // The script will compile here in the first execution
\u script.RunAsync(新全局变量{A=A,B=B});//脚本将在第一次执行时在此编译
如果每次应用程序都要加载一些脚本,那么这是最容易做到的。但是,更好的解决方案是使用单独的AppDomain并单独加载脚本。以下是一种方法:

将脚本执行器代理创建为MarshalByRefObject:

public class ScriptExecutor<TP, TR> : CrossAppDomainObject, IScriptExecutor<TP, TR>
{
    private readonly Script<TR> _script;
    private int _currentClients;

    public DateTime TimeStamp { get; }
    public int CurrentClients => _currentClients;
    public string Script => _script.Code;

    public ScriptExecutor(string script, DateTime? timestamp = null, bool eagerCompile = false)
    {
        if (string.IsNullOrWhiteSpace(script))
            throw new ArgumentNullException(nameof(script));

        var opts = ScriptOptions.Default.AddImports("System");
        _script = CSharpScript.Create<TR>(script, opts, typeof(Host<TP>)); // Other options may be needed here
        if (eagerCompile)
        {
            var diags = _script.Compile();
            Diagnostic firstError;
            if ((firstError = diags.FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error)) != null)
            {
                throw new ArgumentException($"Provided script can't compile: {firstError.GetMessage()}");
            }
        }
        if (timestamp == null)
            timestamp = DateTime.UtcNow;
        TimeStamp = timestamp.Value;
    }

    public void Execute(TP parameters, RemoteCompletionSource<TR> completionSource)
    {
        Interlocked.Increment(ref _currentClients);
       _script.RunAsync(new Host<TP> {Args = parameters}).ContinueWith(t =>
       {
           if (t.IsFaulted && t.Exception != null)
           {
               completionSource.SetException(t.Exception.InnerExceptions.ToArray());
               Interlocked.Decrement(ref _currentClients);
           }
           else if (t.IsCanceled)
           {
               completionSource.SetCanceled();
               Interlocked.Decrement(ref _currentClients);
           }
           else
           {
               completionSource.SetResult(t.Result.ReturnValue);
               Interlocked.Decrement(ref _currentClients);
           }
       });
    }
}

public class Host<T>
{
    public T Args { get; set; }
}
公共类ScriptExecutor:CrossAppDomainObject,IScriptExecutor
{
私有只读脚本\u脚本;
私人国际客户;
公共日期时间时间戳{get;}
public int CurrentClients=>\u CurrentClients;
公共字符串脚本=>\u Script.Code;
公共ScriptExecutor(字符串脚本,DateTime?timestamp=null,bool=false)
{
if(string.IsNullOrWhiteSpace(脚本))
抛出新ArgumentNullException(nameof(script));
var opts=ScriptOptions.Default.AddImports(“系统”);
_script=CSharpScript.Create(script,opts,typeof(Host));//此处可能需要其他选项
如果(编译)
{
var diags=_script.Compile();
诊断错误;
if((firstError=diags.FirstOrDefault(d=>d.Severity==DiagnosticSeverity.Error))!=null)
{
抛出新ArgumentException($“提供的脚本无法编译:{firstError.GetMessage()}”);
}
}
if(时间戳==null)
timestamp=DateTime.UtcNow;
TimeStamp=TimeStamp.Value;
}
public void Execute(TP参数,RemoteCompletionSource completionSource)
{
联锁增量(参考当前客户端);
_script.RunAsync(新主机{Args=parameters}).ContinueWith(t=>
{
if(t.IsFaulted&&t.Exception!=null)
{
SetException(t.Exception.InnerExceptions.ToArray());
联锁。减量(参考);
}
否则,如果(t.IsCanceled)
{
completionSource.setCancelled();
联锁。减量(参考);
}
其他的
{
completionSource.SetResult(t.Result.ReturnValue);
联锁。减量(参考);
}
});
}
}
公共类主机
{
公共T参数{get;set;}
}
创建代理对象以在脚本执行应用程序域和主域之间共享数据:

public class RemoteCompletionSource<T> : CrossAppDomainObject
{
    private readonly TaskCompletionSource<T> _tcs = new TaskCompletionSource<T>();

    public void SetResult(T result) { _tcs.SetResult(result); }
    public void SetException(Exception[] exception) { _tcs.SetException(exception); }
    public void SetCanceled() { _tcs.SetCanceled(); }

    public Task<T> Task => _tcs.Task;
}
公共类RemoteCompletionSource:CrossAppDomainObject
{
私有只读TaskCompletionSource_tcs=new TaskCompletionSource();
public void SetResult(T result){u tcs.SetResult(result);}
public void SetException(Exception[]Exception){{u tcs.SetException(Exception);}
public void setcancelled(){u tcs.setcancelled();}
公共任务任务=>\u tcs.Task;
}
创建此帮助器抽象类型,所有其他远程帮助器都需要从中继承:

public abstract class CrossAppDomainObject : MarshalByRefObject, IDisposable
{

    private bool _disposed;

    /// <summary>
    /// Gets an enumeration of nested <see cref="MarshalByRefObject"/> objects.
    /// </summary>
    protected virtual IEnumerable<MarshalByRefObject> NestedMarshalByRefObjects
    {
        get { yield break; }
    }

    ~CrossAppDomainObject()
    {
        Dispose(false);
    }

    /// <summary>
    /// Disconnects the remoting channel(s) of this object and all nested objects.
    /// </summary>
    private void Disconnect()
    {
        RemotingServices.Disconnect(this);

        foreach (var tmp in NestedMarshalByRefObjects)
            RemotingServices.Disconnect(tmp);
    }

    public sealed override object InitializeLifetimeService()
    {
        //
        // Returning null designates an infinite non-expiring lease.
        // We must therefore ensure that RemotingServices.Disconnect() is called when
        // it's no longer needed otherwise there will be a memory leak.
        //
        return null;
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        Disconnect();
        _disposed = true;
    }
}
公共抽象类CrossAppDomainObject:MarshallByRefObject,IDisposable
{
私人住宅;
/// 
///获取嵌套对象的枚举。
/// 
受保护的虚拟IEnumerable NestedMarshalbyRefObject
{
获取{yield break;}
}
~CrossAppDomainObject()
{
处置(虚假);
}
/// 
///断开此对象和所有嵌套对象的远程处理通道的连接。
/// 
私有无效断开连接()
{
远程服务。断开(此);
foreach(NestedMarshalbyRefObject中的var tmp)
远程服务。断开连接(tmp);
}
公共密封覆盖对象初始化ElifetimeService()
{
//
//返回null表示无限期的未到期租约。
//因此,我们必须确保在以下情况下调用RemotingServices.Disconnect()
//不再需要它,否则将出现内存泄漏。
//
返回null;
}
公共空间处置()
{
总干事(本);
处置(真实);
}
受保护的虚拟void Dispose(bool disposing)
{
如果(_)
返回;
断开连接();
_这是真的;
}
}
<
public static IScriptExecutor<T, R> CreateExecutor<T, R>(AppDomain appDomain, string script)
    {
        var t = typeof(ScriptExecutor<T, R>);
        var executor = (ScriptExecutor<T, R>)appDomain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false, BindingFlags.CreateInstance, null, 
            new object[] {script, null, true}, CultureInfo.CurrentCulture, null);
        return executor;
    }

public static AppDomain CreateSandbox()
{
    var setup = new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase };
    var appDomain = AppDomain.CreateDomain("Sandbox", null, setup, AppDomain.CurrentDomain.PermissionSet);
    return appDomain;
}

string script = @"int Square(int number) {
                      return number*number;
                  }
                  Square(Args)";

var domain = CreateSandbox();
var executor = CreateExecutor<int, int>(domain, script);

using (var src = new RemoteCompletionSource<int>())
{
    executor.Execute(5, src);
    Console.WriteLine($"{src.Task.Result}");
}