简体   繁体   中英

Excel COM object not being released

I am trying to have a clean excel break once I load a user's settings file. I am running into something I can't figure out other than it has something to do with the dictionaries. If I comment out one (or both) of the Dictionary filling brackets, the settings file loads and then releases, but if both are running, the excel app won't release. Am I also tying excel to the dictionaries by sourcing the data from them?

I am sure there are other ways to create global dictionaries, but this is the only way I am confidant with at the moment, but I am willing to learn if something better is out there.

The "dictionary filling code" is the for loops with i & j:

for (int i = 0; i < lastRow - 1; i++)
    {
        string key = settingsSheet.Range["B" + (i + 2)].Value; 
        string value = settingsSheet.Range["A" + (i + 2)].Value; 
        DictionaryLoad.DIC.Add(key, value);
    }

here is the full code:

public Form1()
    {
        InitializeComponent();
        txtFileNamePreface.Enabled = false;
        string fileName = "F:\\Shared\\Projects\\State Assoc Clients\\Data Management\\Download Site\\KeyStats Download Statistics\\Naming Conventions.xls";
        LoadProductName(fileName);
    }

    public static class DictionaryLoad
    {
        public static IDictionary<string, string> DIC;
        public static IDictionary<string, string> DIC2;
        static DictionaryLoad()
        {
            DIC = new Dictionary<string, string>();
            DIC2 = new Dictionary<string, string>();
        }
    }

    private void LoadProductName(string fileName)
    {
        //starting up and defining the Excel references
        Excel.Application excelApp = new Excel.Application(); //excel open here
        Excel.Workbook settingsBook = null;
        Excel.Worksheet settingsSheet = null;
        excelApp.Visible = false;
        excelApp.DisplayAlerts = false;

        settingsBook = excelApp.Workbooks.Open(fileName);
        settingsSheet = settingsBook.Sheets["NamingConventions"]; 

        int lastRow = findFirstBlankRow(settingsSheet, "A1") - 1;
        fillComboBox(cbProductType, lastRow, settingsSheet, "A"); 
        fillComboBox(cbYear, lastRow, settingsSheet, "D");

        int lastRow2 = findFirstBlankRow(settingsSheet, "E1");
        fillComboBox(cbRule, lastRow2, settingsSheet, "E");

        for (int i = 0; i < lastRow - 1; i++)
        {
            string key = settingsSheet.Range["B" + (i + 2)].Value; 
            string value = settingsSheet.Range["A" + (i + 2)].Value; 
            DictionaryLoad.DIC.Add(key, value);
        }

        cbProductName.Items.Clear();
        foreach (KeyValuePair<string, string> entry in DictionaryLoad.DIC)
        {
            if (entry.Value == cbProductType.Text)
            { cbProductName.Items.Add(entry.Key); }
        }
        try { cbProductName.SelectedIndex = 0; }
        catch { }

        for (int j = 0; j < lastRow - 1; j++)
        {
            string key = settingsSheet.Range["B" + (j + 2)].Value; 
            string value = settingsSheet.Range["C" + (j + 2)].Value;
            DictionaryLoad.DIC2.Add(key, value);
        }

        cbRule.SelectedIndex = 0;
        cbYear.Text = DateTime.Now.Year.ToString();
        cbQuarter.SelectedIndex = 0;
        cbMonth.Text = DateTime.Now.ToString("MMMM");
        cbProductType.SelectedIndex = 0;
        string workBookName = excelApp.ActiveWorkbook.FullName;
        txtOutputFolder.Text = Path.GetDirectoryName(workBookName);

        settingsBook.Close();
        excelApp.Quit();
        appCleanup(excelApp);
        appCleanup(settingsBook);
        appCleanup(settingsSheet);
        garbageCleanup();
        Application.Exit();
    }
   public void appCleanup(object application1, object application2 = null, object application3 = null)
    {
        Marshal.ReleaseComObject(application1);
        application1 = null;
    }

    public void garbageCleanup()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
  1. You never need to call Marshal.ReleaseComObject in this context. The runtime is perfectly able to keep track of COM objects and release them when they are no longer referenced. Calling Marshal.ReleaseComObject is a confusing anti-pattern that sadly even some Microsoft documentation mistakenly suggests.

    In your case, this means those calls in appCleanup should be removed, though you might still have to set application1 = null to clear the reference - your code does not should how this variable gets used.

  2. You should call the garbage collector routine twice - you might have cases where the references form a cycle, and the first GC call will break the cycle, but the COM objects might only get properly released on the second call.

  3. Finally, you have to be careful with this kind of code in debug builds. References in a method are artificially kept alive until the end of the method so that they will still be accessible in the debugger. This means your local excelApp variable won't be cleaned up by calling the GC inside that method. To avoid this issue, you might follow a pattern like this:

     public void LoadProductNameAndCleanup(string fileName) { LoadProductName(fileName); garbageCleanup(); garbageCleanup(); } 

I think maybe you should use string.Copy, because otherwise your dictionaries are holding a reference to string objects linked to the Excel objects. This will create a new instances of the string objects:

for (int i = 0; i < lastRow - 1; i++)
{
    string key = string.Copy(settingsSheet.Range["B" + (i + 2)].Value); 
    string value = string.Copy(settingsSheet.Range["A" + (i + 2)].Value); 
    DictionaryLoad.DIC.Add(key, value);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM