Search code examples
c#listviewgarbage-collectionimagelist

Call garbage collector after ImageList.Items.Clear()?


#Context:

Generating PNGs from PDF pages and showing them in a ListView through an ImageList

#Desired behaviour:

When the user chooses another file to be displayed in the listview -> clear both the ImageList and ListView items (obvious) and also delete the generated image files.

#Issue:

Exception thrown on delete: "The process cannot access the file 'X.png' because it is being used by another process."

previewImageList.Images.Clear();
previewListView.Items.Clear();

// folder containing the generated PNGs, created at runtime
string folderToDelete = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(selectedSong));

try
{                
  Directory.Delete(folderToDelete, true);  // Image file "X.png" is used by another process
}
catch (Exception)
{}

Solution found:

  • calling the Garbage Collector manually before deleting the said PNG files seems to do the trick.

    previewImageList.Images.Clear(); previewListView.Items.Clear();

    string folderToDelete = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(selectedSong));

    try { GC.Collect(); GC.WaitForPendingFinalizers();

    Directory.Delete(folderToDelete, true);
    

    } catch (Exception ex) { MessageBox.Show(ex.Message); }

But as i researched so far it isn't a good thing to do. Do I have an alternative to calling the Garbage collector? The reason I need to delete the PNGs is to be able to reuse respective filename with a PDF from another path...

The closest I got to are here and here but didn't help much.

EDIT additional details Generating the PNGs:

  • i use ImageMagik like this:

    startInfo = new ProcessStartInfo(); startInfo.FileName = """ + UtilitiesFolder + "\convert.exe""; startInfo.Arguments = """ + selectedPDFFile.pdf" + "" " + " -resample 168x140 "" + Path.Combine(outputFolder, selectedPDFFileShortName) + ".png" + """; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.RedirectStandardError = true; startInfo.RedirectStandardOutput = true;

    process = Process.Start(startInfo); process.WaitForExit(5000); // wait no more than 5 seconds

    if (process.HasExited) { //good } else{ //abort everything }

Loading images into ImageList & ListView:

void previewSelectedPresentation(string presentationName)
{
  previewImageList.Images.Clear();
  previewListView.Items.Clear();
            
  DirectoryInfo dir = new DirectoryInfo(@"E:\\test\\" + presentationName);
            
  foreach (FileInfo file in dir.GetFiles())
  {
    try
    {
      this.previewImageList.Images.Add(Image.FromFile(file.FullName));
    }
    catch
    {
      Console.WriteLine("This is not an image file");
    }
  }

  for (int j = 0; j < this.previewImageList.Images.Count; j++)
  {
    ListViewItem item = new ListViewItem();
    item.ImageIndex = j;
    item.Text = (j + 1).ToString();                
    this.previewListView.Items.Add(item);
  }
}

Solution

  • Problem solved. Per Hans Passant above, (paraphrasing)

    This went wrong a long time ago. I forgot to dispose the images after I added them to the ImageList.

    So here's the correct way to add into ImageList (please see original question for comparison) - changes signaled by comment:

    void previewSelectedPresentation(string presentationName)
    {
      previewImageList.Images.Clear();
      previewListView.Items.Clear();
    
      DirectoryInfo dir = new DirectoryInfo(@"E:\\test\\" + presentationName);
    
      foreach (FileInfo file in dir.GetFiles())
      {
        try
        {  
           // these three lines have changed
           Image slideImage = Image.FromFile(file.FullName);  // change 1
           this.previewImageList.Images.Add(slideImage);      // change 2
           slideImage.Dispose();                              // change 3
        }
        catch
        {
          Console.WriteLine("This is not an image file");
        }
      }
    
      for (int j = 0; j < this.previewImageList.Images.Count; j++)
      {
        ListViewItem item = new ListViewItem();
        item.ImageIndex = j;
        item.Text = (j + 1).ToString();                
        this.previewListView.Items.Add(item);
      }          
    }
    

    Cleaning up works now without calling the GC. Thanks for helping me figure this out myself! Also, helpful answer here.