Excel COM 对象未被释放
Excel COM object not being released
我试图在加载用户设置文件后彻底中断 excel。我 运行 陷入了一些我想不通的事情,除了它与字典有关。如果我注释掉一个(或两个)字典填充括号,设置文件加载然后释放,但如果两者都是 运行,excel 应用程序将不会释放。我是否也通过从词典中获取数据来将 excel 绑定到词典?
我相信还有其他方法可以创建全局词典,但这是我目前唯一信任的方法,但如果有更好的方法,我愿意学习。
"dictionary filling code" 是 i 和 j 的 for 循环:
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);
}
完整代码如下:
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();
}
我认为也许您应该使用 string.Copy,否则您的词典将保存对链接到 Excel 对象的字符串对象的引用。这将创建字符串对象的新实例:
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);
}
您永远不需要在这种情况下调用 Marshal.ReleaseComObject
。运行时完全能够跟踪 COM 对象并在不再引用它们时释放它们。调用 Marshal.ReleaseComObject
是一种令人困惑的反模式,令人遗憾的是,甚至某些 Microsoft 文档也错误地建议了这种模式。
在你的情况下,这意味着应该删除 appCleanup
中的那些调用,尽管你可能仍然需要设置 application1 = null
来清除引用 - 你的代码不应该如何使用这个变量.
您应该调用垃圾收集器例程两次 - 您可能会遇到引用形成循环的情况,第一次 GC 调用将打破循环,但 COM 对象可能只会在第二次通话。
最后,在调试版本中你必须小心这种代码。方法中的引用被人为地保持活动状态,直到方法结束,以便它们仍然可以在调试器中访问。这意味着您的本地 excelApp
变量不会通过在该方法内调用 GC 来清理。
为避免此问题,您可以遵循如下模式:
public void LoadProductNameAndCleanup(string fileName)
{
LoadProductName(fileName);
garbageCleanup();
garbageCleanup();
}
我试图在加载用户设置文件后彻底中断 excel。我 运行 陷入了一些我想不通的事情,除了它与字典有关。如果我注释掉一个(或两个)字典填充括号,设置文件加载然后释放,但如果两者都是 运行,excel 应用程序将不会释放。我是否也通过从词典中获取数据来将 excel 绑定到词典?
我相信还有其他方法可以创建全局词典,但这是我目前唯一信任的方法,但如果有更好的方法,我愿意学习。
"dictionary filling code" 是 i 和 j 的 for 循环:
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);
}
完整代码如下:
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();
}
我认为也许您应该使用 string.Copy,否则您的词典将保存对链接到 Excel 对象的字符串对象的引用。这将创建字符串对象的新实例:
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);
}
您永远不需要在这种情况下调用
Marshal.ReleaseComObject
。运行时完全能够跟踪 COM 对象并在不再引用它们时释放它们。调用Marshal.ReleaseComObject
是一种令人困惑的反模式,令人遗憾的是,甚至某些 Microsoft 文档也错误地建议了这种模式。在你的情况下,这意味着应该删除
appCleanup
中的那些调用,尽管你可能仍然需要设置application1 = null
来清除引用 - 你的代码不应该如何使用这个变量.您应该调用垃圾收集器例程两次 - 您可能会遇到引用形成循环的情况,第一次 GC 调用将打破循环,但 COM 对象可能只会在第二次通话。
最后,在调试版本中你必须小心这种代码。方法中的引用被人为地保持活动状态,直到方法结束,以便它们仍然可以在调试器中访问。这意味着您的本地
excelApp
变量不会通过在该方法内调用 GC 来清理。 为避免此问题,您可以遵循如下模式:public void LoadProductNameAndCleanup(string fileName) { LoadProductName(fileName); garbageCleanup(); garbageCleanup(); }