CSharpCodeProvider problema de memória
-
quinta-feira, 12 de abril de 2012 20:42
Olá.
Eu tenho uma lista de códigos para compilar e executar em runtime. Estou usando o CsharpCodeProvider para isso, mas eu estava olhando e ele está apenas consumindo memória e não liberando ela.
Esse é o código que compila meus códigos:
public bool Compile(string script) { CSharpCodeProvider codeprovider = new CSharpCodeProvider(); ICodeCompiler icc = codeprovider.CreateCompiler(); CompilerParameters cp = new CompilerParameters(); cp.ReferencedAssemblies.Add("System.dll"); cp.ReferencedAssemblies.Add(System.Reflection.Assembly.GetExecutingAssembly().Location); cp.TreatWarningsAsErrors = false; cp.MainClass = "CodesRun"; cp.CompilerOptions = "/target:library /optimize"; cp.GenerateExecutable = false; cp.GenerateInMemory = false; //it was true, but same problem TempFileCollection tfc = new TempFileCollection(Application.StartupPath, false); CompilerResults cr = new CompilerResults(tfc); cr = icc.CompileAssemblyFromSource(cp, script); if (cr.Errors.Count > 0) { //Error } else { Assembly assembly = cr.CompiledAssembly; IScript teste = (IScript)assembly.CreateInstance("CodesRun.Script"); teste.Run(); } tfc.Delete(); codeprovider.Dispose(); return true; }Quando eu abro o programa ele fica em 50.000k de memoria usada no "Gerenciador de tarefas" na lista de processos, quando eu ativo o timer que fica compilando e executando a lista de códigos, a memória no gerenciador de tarefas começa a crescer infinitamente, após uns 10 minutos do programa rodando com o timer ele chega a ficar com 500.000k+ de memória em uso.
Alguém poderia me ajudar a arrumar isso? Ou alguma maneira de evitar tal problema usando co CSharpCodeProvider.
Obrigado.
Todas as Respostas
-
sexta-feira, 13 de abril de 2012 00:52
Boa noite,
considere esta sugestão somente para este contexto, não é trivial. Sugiro que você atribua null para as suas variáveis cp, icc, tfc e cr ao final do método, e por fim execute o comando GC.Collect().
Prepare-se para um overhead no processamento por forçar o uso constante do Garbage Collector, mas creio que possa resolver seu problema com a alocação de memória.
Esta aplicação será executada pontualmente, certo ? Caso contrário creio que seja necessário buscar outra solução menos impactante.
Abraços,
Daniel Cheida de Oliveira
-
sexta-feira, 13 de abril de 2012 01:15
Olá.
Aparentemente jogando todos objetos como null e chamando o GC.Collect() diminuiu um pouco a velocidade em quem a memória é consumida, porém ela ainda continua a acontecer.
Sim, os códigos são executados o tempo todo dentro de um timer de 500ms, todos códigos só são executados novamente quando termina a execução antiga, para evitar vários códigos iguais rodando ao mesmo tempo.
Obrigado por enquanto.
-
sexta-feira, 13 de abril de 2012 01:32
Boa noite,
o que acontece é que quando o código é compilado ele gera um Assembly, que é carregado em memória, e que não pode ser descarregado. O resultado é uma espécie de Memory Leak.
Creio que seja interessante analisar a necessidade de trabalhar desta forma. Imagino que você tenha utilizado esta arquitetura para fazer Late Binding desses seus scripts. Sendo este o caso sugiro que você implemente as chamadas de outra forma, tendo seus módulos já compilados e os carregando dinamicamente num AppDomain. Então você poderia facilmente remover o módulo da memória após a execução.
Segue um exemplo:
private static void CallDoSomething(string assemblyPath, Type interfaceType, string methodName, object[] parameters) { Assembly assembly = Assembly.LoadFrom(assemblyPath); Type t = assembly.GetTypes().Where(x=>x.GetInterfaces().Count(y=>y==interfaceType)>0).FirstOrDefault(); if (t == null) { throw new ApplicationException("No type implements this interface"); } MethodInfo mi = t.GetMethods().Where(x => x.Name == methodName).FirstOrDefault(); if (mi == null) { throw new ApplicationException("No such method"); } mi.Invoke(Activator.CreateInstance(t), parameters); }O que você acha ?
Abraços,
Daniel Cheida de Oliveira
- Sugerido como Resposta Daniel Cheida sexta-feira, 13 de abril de 2012 01:49
- Não Sugerido como Resposta Daniel Cheida sexta-feira, 13 de abril de 2012 02:30
-
sexta-feira, 13 de abril de 2012 02:09
Olá.
Parece bem promissora essa ideia, só não entendi o que esse método substituiria, pois fiz uns testes e a memória é consumida ao executar a linha:
cr = icc.CompileAssemblyFromSource(cp, script);
Então como eu disse, não entendi direito aonde entraria o CallDoSomething acima.
Eu li algo sobre o AppDomain, mas não achei nenhum exemplo de implantação do mesmo, pois aparentemente ele resolveria o caso pelas minhas pesquisas.
Obrigado novamente.
-
sexta-feira, 13 de abril de 2012 02:47
Exatamente,
o problema com a compilação em tempo de execução é que ela gera um Assembly em memória, e você não tem controle suficiente para removê-lo de lá antes de terminar a sua aplicação toda.
A solução que lhe mostrei utiliza não faz compilação alguma, ela simplesmente carrega o Assembly em um novo AppDomain, que é executado em um processo a parte.
O objetivo do AppDomain é o isolamento, ou seja, tudo o que é carregado em um novo AppDomain é executado em um processo separado, e ao término da execução você pode descarregar o AppDomain, e o processo será extinto.
Outra vantagem do isolamento é que um de seus módulos pode gerar uma exceção, e estando em um AppDomain separado você pode prosseguir com a execução dos demais módulos.
Segue um código mais adaptado à sua necessidade:
using System; using System.Reflection; namespace ConsoleApplication1 { interface IScript { void Run(); } class Loader : MarshalByRefObject { private Assembly _assembly; public override object InitializeLifetimeService() { return null; } public void Load(string path_) { _assembly = Assembly.Load(AssemblyName.GetAssemblyName(path_)); } public void Run() { var type = _assembly.GetType("CodesRun.Script"); var module = Activator.CreateInstance(type) as IScript; type.GetMethod("Run").Invoke(module, null); } } class Program { static void Main(string[] args) { var domain = AppDomain.CreateDomain("execution"); var loader = domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName) as Loader; loader.Load("ClassLibrary.dll"); loader.Run(); AppDomain.Unload(domain); } } }Repare que utilizei uma classe chamada Loader para realizar a execução do método Run. O objetivo é manter o isolamento entre o módulo carregado no AppDomain de execução e o objeto que dispara a execução (carregado no AppDomain corrente).
Desta forma você deverá manter seus módulos compilados e executá-los pelo caminho da dll. Lembrando que os módulos devem implementar a interface IScript.
Vale ressaltar que a classe Loader herda MarshalByRefObject para que possa ser utilizada entre Application Domains distintos.
Abraços,
Daniel Cheida de Oliveira
- Sugerido como Resposta Daniel Cheida sexta-feira, 13 de abril de 2012 02:50
- Editado Daniel Cheida sexta-feira, 13 de abril de 2012 02:54
- Marcado como Resposta Levi DomingosMicrosoft Community Contributor, Moderator domingo, 15 de abril de 2012 22:52
-
sexta-feira, 13 de abril de 2012 15:29
Olá.
Mas dessa forma eu teria de gerar a .dll, não? Pois não estou gerando nenhum arquivo de Output, pois se eu fizer isso, ao executar os outros códigos todos tentaria acessar o mesmo arquivo e daria problemas.
Tentei aplicar tais métodos em meus códigos do primeiro post, mas sem sucesso, pois ainda não entendi de onde sairá esse Assembly em que ele carregará.
Pois estava analisando o código e o consumo de memória começa ao executar essa linha:
cr = icc.CompileAssemblyFromSource(cp, script);
Que é a que gera o Assembly, então mesmo que eu coloque um return depois dela para não executar o código ele continua consumindo memória.
Obrigado novamente.
- Editado Kyory sexta-feira, 13 de abril de 2012 15:45
-
sábado, 14 de abril de 2012 16:01
Boa tarde,
sim, você teria que manter seus scripts compilados, e com nomes de arquivo diferentes.
Abraços,
Daniel Cheida de Oliveira
-
domingo, 15 de abril de 2012 23:58
Olá.
Infelizmente será complicado manter os arquivos, já que as pessoas podem alterar o código a qualquer hora, terei de ver o que fazer.
Farei alguns testes, mas tentarei encontrar um jeito de usar o AppDomain que aparentemente é a solução, mas sem ter de manter os arquivos.
Obrigado.
-
segunda-feira, 16 de abril de 2012 16:43
Boa tarde,
o que você pode fazer é gerar a dll em tempo de execução. Particularmente não acho uma solução muito elegante, mas no seu contexto creio que seja útil. Para fazer isso você pode chamar o compilador do C# por linha de comando.
Segue exemplo:
string script = "Script1"; var processStart = new ProcessStartInfo(@"c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319", string.Format("/out {0}.dll {0}.cs", script)); processStart.RedirectStandardOutput = true; processStart.RedirectStandardError = true; processStart.CreateNoWindow = true; var compiler = new Process(); compiler.StartInfo = processStart; compiler.Start(); compiler.WaitForExit(); //Aguardando a execução terminar antes de prosseguir if (compiler.ExitCode != 0) { throw new Exception(String.Format("Erro ao compilar o script {0}", script)); }Mais detalhes aqui: http://msdn.microsoft.com/en-us/library/ms379563(v=vs.80).aspx
Abraços,
Daniel Cheida de Oliveira
- Sugerido como Resposta Daniel Cheida segunda-feira, 16 de abril de 2012 16:44
-
segunda-feira, 16 de abril de 2012 18:55
Olá.
Então, esse serial o principal problema, já que teria de manter X arquivos diferentes para serem executados, já que vários códigos podem rodar simultaneamente, usar arquivos para tal criaria um problema com arquivos em uso, já que um código pode demorar 30 segundos para começar e terminar de executar e outro poderia demorar 1 segundo, ou seja, esse código de 1 segundo iria tentr executar porém o arquivo estaria em uso pelo código de 30 segundos.
Pelas minhas pesquisas o AppDomain será a única solução, só tenho que pensar agora como fica esse caso dos arquivos, pois o interessante mesmo seria nem gerar os arquivos já que como eu disse, os usuários podem alterar o código a ser executado e poderia causar problemas tendo de gerar/deletar arquivos etc.
Obrigado.

