Concepts of Mesomorphic Allocation and Liberation in C#

Asked

Viewed 1,211 times

11

I’m making a web application that has a finite recursive loop. But during the process the memory consumption is constantly growing and at the end Visual Studio has more than 2 Gigas of memory use.

I would like to know some concepts about memory allocation and displacement (if those are the terms) and what can cause this immense consumption, or what tricks I can turn to for memory release.

I am not very experienced and I believe that this may be caused by not optimized codes (not to say bad ones). Follow my code:

protected void BtnExportar_OnClick(object sender, EventArgs e)
{
    string folder = @"D:\Fichas\";

    DateTime data = new DateTime();
    data = DateTime.Now;
    data = data.Date;
    string dataSoNumeros = new String(data.ToString().Where(Char.IsDigit).ToArray());

    if (!Directory.Exists(folder))
    {
        Directory.CreateDirectory(folder);
    }

    if (!Directory.Exists(folder + dataSoNumeros.Substring(0, 8)))
    {
        Directory.CreateDirectory(folder + dataSoNumeros.Substring(0, 8));
    }

    Stop = false;

    this.IniciarExportacao();
}

public void IniciarExportacao()
{
    IList<Ficha> Fichas = null;

    do
    {
        Fichas = this.ObterFichas();
        this.GerarArquivo(Fichas);
    }
    while (Fichas.Count > 0 && Stop == false);

    detalhesArquivo.Text = "Exportação Concluída.";
}    

public IList<Ficha> ObterFichas()
{        
    DetachedCriteria criteria = DetachedCriteria.For<Ficha>();

    criteria.SetMaxResults(1);

    criteria.CreateAlias("exportacao", "e",NHibernate.SqlCommand.JoinType.LeftOuterJoin).Add(Restrictions.IsNull("e.Exportado"));

    var fichasObtidas = DomainService.FichaRepository().LoadAll(criteria);

    return fichasObtidas;
}

public void GerarArquivo(IList<Ficha> Fichas)
{
    msgLog = new StringBuilder();        

    msgLog_Inicio  = "    - Iniciando Exportação da Ficha " + Fichas[0].IdFicha + " / Conta " + Fichas[0].Conta.IdConta + "...";
    msgLog_Gerando = "    - Gerando imagem do arquivo...";        

    foreach (var index in Fichas)
    {
        #region DEFAULT

        byte[] arqB = null;
        string nomarq = "";
        string saveLocation = null;
        System.Drawing.Image imageArquivo = null;

        arqB = index.Arquivo;
        nomarq = index.Conta.IdConta + "_" + index.IdFicha;

        DateTime data = new DateTime();
        data = DateTime.Now;
        data = data.Date;
        string dataSoNumeros = new String(data.ToString().Where(Char.IsDigit).ToArray());

        saveLocation = @"D:\fichas\" + dataSoNumeros.Substring(0, 8) + @"\" + nomarq + ".jpg";

        Ficha ficha = new Ficha();
        Exportacao fichaExportacao = new Exportacao();

        ficha = index;
        fichaExportacao.Ficha = index;
        fichaExportacao.IdFicha = index.IdFicha;            
        fichaExportacao.DataExportacao = DateTime.Now;
        fichaExportacao.IdUsuarioExportador = ControlUsuario.GetSession.Usuario.IdUsuario;

        #endregion

        try
        {
            imageArquivo = this.byteArrayToImage(arqB);
            imageArquivo.Save(saveLocation, System.Drawing.Imaging.ImageFormat.Jpeg);
            fichaExportacao.Exportado = 1;
            msgLog_Imagem = "    - Arquivo gerado na pasta "+ saveLocation +"";
        }
        catch (Exception)
        {
            fichaExportacao.Exportado = 0;
            msgLog_Imagem = "    - O arquivo está corrompido e não foi gerado...";
        }

        msgLog.AppendLine(msgLog_Inicio);
        msgLog.AppendLine(msgLog_Gerando);
        msgLog.AppendLine(msgLog_Imagem);

        this.AtualizarFicha(ficha, fichaExportacao, saveLocation, msgLog);            
    }
}

public void AtualizarFicha(Ficha ficha, Exportacao fichaExportacao, string saveLocation, StringBuilder msgLog)
{
    if (ficha.exportacao == null)
    {
        try
        {
            DomainService.ExportacaoRepository().SaveNew(fichaExportacao);
            msgLog_Update = "    - Atualizando registro no banco de dados...";
            msgLog_Usuario = "    - Exportação concluída por " + ControlUsuario.GetSession.Usuario.Nome + ".";
        }
        catch (Exception)
        {
            msgLog_Update = "    - Falha ao atualizar banco de dados...";
            msgLog_Usuario = "    - Exportação não pôde ser concluída. Exportador: " + ControlUsuario.GetSession.Usuario.Nome + ".";
        }
    }
    else
    {
        try
        {
            DomainService.ExportacaoRepository().Update(fichaExportacao);
            msgLog_Update = "    - Atualizando registro no banco de dados...";
            msgLog_Usuario = "    - Exportação concluída por " + ControlUsuario.GetSession.Usuario.Nome + ".";
        }
        catch (Exception)
        {
            msgLog_Update = "    - Falha ao atualizar banco de dados...";
            msgLog_Usuario = "    - Exportação não pôde ser concluída. Exportador: " + ControlUsuario.GetSession.Usuario.Nome + ".";
        }

    }

    msgLog.AppendLine(msgLog_Update);
    msgLog.AppendLine(msgLog_Usuario);

    using (StreamWriter w = new StreamWriter(@"D:\fichas\log.txt", true, Encoding.UTF8))
    {                
        Log(msgLog, w);
    }

    using (StreamReader r = File.OpenText(@"D:\fichas\log.txt"))
    {
        DumpLog(r);
    }
}

#region ARQUIVO IMAGEM

public byte[] Arquivo(object sender, EventArgs e)
{
    return base.Cache[base.Request.QueryString["id"]] as byte[];
}  

public byte[] imageToByteArray(System.Drawing.Image imageIn)
{
    using (var ms = new MemoryStream())
    {
        imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        return ms.ToArray();
    }
}

public System.Drawing.Image byteArrayToImage(byte[] byteArrayIn)
{
    MemoryStream ms = new MemoryStream(byteArrayIn);
    System.Drawing.Image returnImage = System.Drawing.Image.FromStream(ms);
    return returnImage;
}

#endregion

#region LOGS

public static void Log(StringBuilder msgLog, TextWriter w)
{
    w.WriteLine("[{0} {1}] ---------------------------------------", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
    w.WriteLine("{0}", msgLog.ToString());
}

public static void DumpLog(StreamReader r)
{
    string line;
    while ((line = r.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

#endregion
  • 2

    Can you post the code that is producing this problem? Much better to give accurate advice that solves the problem than to guess possible causes randomly.

  • At first I think it would be more interesting for me (and future readers) to better understand how the "wheel" works. But I will post the code.

  • Just one possibility: Comment on all the lines you write in the log and try again. You’re keeping the log whole in memory. Any significant changes? Other: what order are we talking about here? Thousands of chips? Millions?

  • 21 thousand chips. Each one with an image field registered in the bank.

  • I did not find any recursion in this code! But it seems that you are putting a lot of images in memory, no?

  • There’s a do/while that repeats 22,000 times. But I think you might be putting the images in memory, yes. I just don’t know how to avoid it, so I figured I’d ask.

  • 1

    The do/while is just a loop, it is not recursion. But what all indicates the problem is even with the images.

Show 2 more comments

3 answers

11


You got two problems here.

The first is the images that can remain indefinitely in memory. You do not call the method Dispose() of them and lets their reference fall out of scope. From that point the memory allocated to the image will be released only when the method Finalize() is called by the Garbage Collector. And GC is usually only called when there is a need to allocate memory, not always. So it is possible that the memory will only be released after all the loops run. The solution of this? Call Dispose() at the end of each loop!

MSDN: Always call Dispose before you release your last Reference to the Image. Otherwise, the Resources it is using will not be Freed until the Garbage Collector calls the Image Object’s Finalize method.

The second is with the log. You are keeping it with a StringBuilder in memory. Thus, when the loop of the last record is running you will have a huge string saved. The correct thing with logs is to write it directly to files. Besides, if everything fails and your program crashes, you will have the log saved. Open a file at the beginning of the process and write the messages directly in that file.

  • 1

    It was exactly the issue of Log. I’m already writing it in a file, however I was opening and reading this file at each loop. Since I only have to read it once at the end of the process.

6

Your variable imageArquivo is the type System.Drawing.Bitmap. That kind inherits from System.Drawing.Image, implementing the interface IDIsposable. Hence you don’t call the method Dispose of these images nowhere, this may be the cause of the exaggerated memory consumption. Images do not stay in the memory space managed by GC ;)

In other words: the Garbage Collector There is no way to know when the memory occupied by the images can be released. It is up to you to indicate to the GC when it can displace that memory. You can do this in two ways: explicitly calling the method Dispose in your images when you are sure they will no longer be used, or create the images using the reserved word using in the same way as with streams, for example.

Remembering:

  • The blocks using call the method Dispose automatically when they’re done;
  • Never try to access an object whose method Dispose has already been called, because the displacement that occurs "behind the scenes" (implemented within the Dispose) can render the object unusable (i.e.: reference to a type Bitmap is still there, but the image even in memory no longer exists).
  • How could I do Dispose of these images? I don’t know how it works.

  • 1

    @Joaopaulo edited the answer to include this information. I suggest studying the method Dispose and the keyword using; Both are very important, for what you are doing now and for much more.

  • Show, I’ll test it here.

  • I did so: using(System.Drawing.Image imageFile = this.byteArrayToImage(arqB)), but kept going up the memory. Does the token object with the picture field I’m loading can be the cause of this too?

  • 1

    Review your method byteArrayToImage. You don’t call the Dispose of Memorystream also.

0

My mistake in question was using this file read method in each loop:

public static void DumpLog(StreamReader r)
{
    string line;
    while ((line = r.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.