2

我有一些代码可以打开电子表格,读取一些值,然后关闭工作表。我需要对多个文件执行此操作。我遇到的问题是 Excel 应用程序实例没有退出,因此当我为多个文件运行我的进程时,我最终会运行几个 excel.exe 进程。任何想法如何让 Excel 关闭?

    static void ParseFile(string file)
    {
        try
        {
            System.Console.WriteLine("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
            for (int i = 2; i < 27; i++)
            {
                log(ws.Cells[i, 1].Text);
            }
            wb.Close(false);                
            excel.Quit();
            excel = null;
            ws = null;
            wb = null;
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }
    }

------2012 年 12 月 11 日更新--------仍然打开 excel 实例--------

static void ParseFile(string file)
    {
        try
        {
            log("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
    //do some stuff here
            wb.Close(false);
            excel.Quit();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Marshal.FinalReleaseComObject(ws);
            Marshal.FinalReleaseComObject(wb);
            Marshal.FinalReleaseComObject(excel);                
            excel = null;
            ws = null;
            wb = null;
            System.GC.Collect();
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }            
    }
4

4 回答 4

3
  1. 用于Marshal.FinalReleaseComObject()释放资源

  2. 如果您一个接一个地读取多个文件,那么在性能方面最好只打开/关闭 Excel应用程序一次并将打开/关闭应用程序部分移动到单独的函数中

重构代码:

static Excel.Application OpenExcel(){
    Excel.Application excel = null;
    try{
        excel = new Excel.Application();
        return excel;
    }
    catch(Exception ex){
        log(ex.Message);
        return null;
    }
}

static void ParseFile(string file)
{
    try
    {
        System.Console.WriteLine("parsing:" + file);            
        Excel.Workbook wb = excel.Workbooks.Open(file);
        Excel.Worksheet ws = wb.Worksheets[1];
        for (int i = 2; i < 27; i++)
        {
            log(ws.Cells[i, 1].Text);
        }
        wb.Close(false);    
    }
    catch (Exception ex)
    {
        log(ex.Message);
    }
    finally{
        Marshal.FinalReleaseComObject(ws);
        Marshal.FinalReleaseComObject(wb);
        ws = null;
        wb = null;
    }
}

static void CloseExcel(Excel.Application excel){
    try{
        excel.Quit();
    }
    catch(Exception ex){
        log(ex.Message);
    }
    finally{
        Marshal.FinalReleaseComObject(excel);
        excel = null;
    }
}

用法:

Excel.Application excel = OpenExcel();
if(excel != null){
    // Parse files in a loop
    ParseFile("fileName");

    // Close excel after parsing all files
    CloseExcel(excel);
}
于 2012-11-21T17:11:54.677 回答
2

您可以在实现的实际 COM 对象周围使用包装器对象IDisposable,以便它可以与C# 的using语句一起使用。

这样做的好处是它将促进可读代码,并且适用于任何 COM 对象。下面是一个运行时调度的例子:

using System;
using System.Reflection;
using System.Runtime.InteropServices;

public class ComRef<T> : IDisposable where T : class
{
    private T reference;

    public T Reference
    {
        get
        {
            return reference;
        }
    }

    public ComRef(T o)
    {
        reference = o;
    }

    public void Dispose()
    {
        if (reference != null)
        {
            Marshal.ReleaseComObject(reference);
            reference = null;
        }
    }
}

public class Test
{
    static void Main()
    {
        Type excelAppType = Type.GetTypeFromProgID("Excel.Application");
        using (var comRef = new ComRef<object>(Activator.CreateInstance(excelAppType)))
        {
            var excel = comRef.Reference;
            // ...
            excel.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, excel, null);
        }
    }
}

但是,如果您已经导入了 Excel 的类型库或任何其他类型库,那么您可能需要一些更友好的东西:

public class CoClassComRef<T> : ComRef<T> where T : class, new()
{
    public CoClassComRef() : base(new T())
    {
    }
}

public class Test
{
    static void Main()
    {
        using (var comRef = new CoClassComRef<Excel.Application>())
        {
            var excel = comRef.Reference;
            // ...
            excel.Quit();
        }
    }
}

你应该确保你没有捕获到某个超出语句comRef.Reference的字段或变量。using

请注意,我没有过多考虑线程安全和正确的Dispose实现。如果只使用ComRefwithusing语句,线程安全并不重要。一个适当的Dispose实现将与终结器合作,但这里没有必要,因为using基本上是try-finally. 如果你ComRefusing语句中使用 not 并且Dispose没有被调用,ComRef则只会被垃圾回收,并且底层的 COM 对象也会被回收,如果只有 thisComRef引用它,它将被释放。

最后,我没有使用Marshal.FinalReleaseComObject,因为这是在您绝对确定要释放底层 COM 对象(至少,来自托管环境的所有引用)时进行的,无论它已经(重新)进入托管环境多少次世界。但是,如果你觉得幸运,你可以创建一个新的构造函数,它还接收一个布尔值,说明 ifFinalReleaseComObject应该被调用而不是ReleaseComObject. 任何这些方法的网络搜索的第一个结果将指向文章和博客文章,这些文章和博客文章详细说明了为什么它们通常是邪恶的,一个比另一个多。

于 2012-11-22T16:23:49.333 回答
2

最终杀死了进程,这是唯一有效的方法。

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    /// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
    /// <param name="hWnd">Handle to the main window of the process.</param>
    /// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
    public static bool TryKillProcessByMainWindowHwnd(int hWnd)
    {
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        if (processID == 0) return false;
        try
        {
            Process.GetProcessById((int)processID).Kill();
        }
        catch (ArgumentException)
        {
            return false;
        }
        catch (Exception ex)
        {
            return false;
        }            
        return true;
    }

    static void ParseFile(string file)
    {
        try
        {
            log("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
            //do some stuff here
            wb.Close(false);
            int hWnd = excel.Application.Hwnd;
            excel.Quit();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Marshal.FinalReleaseComObject(ws);
            Marshal.FinalReleaseComObject(wb);
            Marshal.FinalReleaseComObject(excel);                
            excel = null;
            ws = null;
            wb = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            TryKillProcessByMainWindowHwnd(hWnd);
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }            
    }
于 2012-12-11T15:41:21.237 回答
1

我想分享一些关于这个问题的经验。

我重新构建了我的 Excel 互操作代码,以确保我永远不会有超过一个属性期,遵循此网页中所述的“规则”:http: //jake.ginnivan.net/vsto-com-interop/

前任:

  • 我删除了如下字符串...

ExcelApplication.ExcelWorkbook.Workbookworksheet.WorksheetRange.Range.Count

(有点夸张)。

-我用以下项目替换了这些字符串......

var CurrentRange = currentMDL.CurrentMDL_xlRange;
var CurrentRangeColumns = CurrentRange.Columns;
var CurrentRangeWorksheet = currentMDL.CurrentMDL_Worksheet;
var CurrentRangeWorksheetCells = CurrentRangeWorksheet.Cells;

从这里,我能够更干净地利用我想要的东西。

前任:

for(int i = 1; i <= CurrentRangeColumns.Count; i++)
{
//Doing stuff
}

在我完成所有操作之后,我确保以打开它的相同方法关闭我的 Excel 文档。我计划重新访问它,看看我是否能够远程关闭 Excel 文档。

最后,我确保跟进我在 Excel 处理方法中使用的所有 COM 对象的一些版本。

前任:

Marshal.FinalReleaseComObject(CurrentRange);
Marshal.FinalReleaseComObject(CurrentRangeCells);
Marshal.FinalReleaseComObject(CurrentRangeRows);

操作顺序在这里很重要。我确保关闭我的工作簿,然后关闭 Excel 应用程序,最后释放了我的 COM 对象。关于 ComObject 版本的使用,我与一位工程师进行了交谈。他说我不应该使用这些调用,因为垃圾收集最终应该会清理我的烂摊子。在这里学习时,我无法通过垃圾收集来关闭我的 Excel 实例并选择自己发布它们。

-克里斯

于 2018-12-06T14:47:49.133 回答