2016-01-20 47 views
3

我一直在嘗試瞭解Roslyn,看看它是否適合我的需求。紋波效應:OutOfMemoryException

在非常簡單的項目,我想創建一個簡單的「漣漪效應」,這是每個迭代產生一個新的組件被加載並

是後最終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; 
} 

Exception Image

回答

0

您使用CSharpScript.Run或評估這恰好是相當大的方法,你實際上是在加載新的腳本(一個.dll)每次。爲了避免這一點,你需要TE緩存,你是這樣做執行腳本:

_script = CSharpScript.Create<TR>(code, opts, typeof(Globals<T>)); // Other options may be needed here 

有_script緩存不妨現在就先執行它:

_script.RunAsync(new Globals<T> {A = a, B = b}); // The script will compile here in the first execution 

如果你有幾個腳本每次加載您的應用程序,這是最容易做的事情。但更好的解決方案是使用單獨的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; } 
} 

創建一個代理對象共享腳本執行應用程序域和主域之間數據:

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; 
} 

創建此幫助程序抽象類型,即所有其他遠程程序需要繼承的類型:

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; 
    } 
} 

下面是我們如何使用它:

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}"); 
} 

注RemoteCompletionSource的使用塊內的使用情況。如果你忘記處理它,你將有內存泄漏,因爲這個對象在另一個域(不是調用者)上的實例永遠不會被GCed。

聲明:我從 here的想法RemoteCompletionSource,也從公共領域CrossAppDomainObject的想法。

0

通過下面的評論(複製下面);關於這個問題;我有很多動態腳本將被加載,而不是緩存,我希望腳本隨後被處理;那可能嗎?

「每當你使用CSharpScript.Run或評估方法,你實際上是在加載新的腳本(一個.dll),這恰好是相當大的。爲了避免這一點,你需要緩存你正在執行的腳本「