Нужно ли освобождать * каждый * объект взаимодействия Excel с помощью Marshal.ReleaseComObject? - PullRequest
28 голосов
/ 28 мая 2010

Редактировать

См. Также Как правильно очистить объекты взаимодействия Excel? . Недавно я столкнулся с этим вопросом, и он дал много понимания проблемы правильной утилизации COM-объектов. Обязательно проверяйте не только первый (отмеченный) ответ, потому что остальные ответы выходят за рамки простого совета «не используйте две точки» и «используйте ReleaseComObject для каждого ком-объекта».

Я снова вернулся к этому вопросу, потому что понял, что, несмотря на тщательную регистрацию и удаление всех моих COM-объектов, мои экземпляры Excel все еще не были должным образом уничтожены. Оказывается, есть способы создания COM-объектов, которые совершенно неочевидны (т. Е. Вы можете пропустить COM-объекты, даже если вы никогда не используете две точки). Кроме того, даже если вы тщательно, если ваш проект выходит за рамки определенного размера, вероятность пропустить COM-объект приближается к 100%. И может быть очень трудно найти того, кого вы пропустили, когда это произойдет. Ответы на вопрос, связанный выше, предоставляют некоторые другие методы, позволяющие убедиться, что экземпляр Excel определенно будет закрыт. Тем временем я сделал небольшое (но существенное) обновление моего ComObjectManager (ниже), чтобы отразить то, что я узнал из вопроса, связанного выше.

Оригинальный вопрос

Я видел несколько примеров, где Marshal.ReleaseComObject() используется с объектами Excel Interop (т.е. объектами из пространства имен Microsoft.Office.Interop.Excel), но я видел, что он использовался в различной степени.

Мне интересно, смогу ли я сойти с рук что-то вроде этого:

var application = new ApplicationClass();
try
{
    // do work with application, workbooks, worksheets, cells, etc.
}
finally
{
    Marashal.ReleaseComObject(application)
}

Или, если мне нужно освободить каждый созданный объект, как в этом методе:

public void CreateExcelWorkbookWithSingleSheet()
{
    var application = new ApplicationClass();
    var workbook = application.Workbooks.Add(_missing);
    var worksheets = workbook.Worksheets;
    for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
    {
        var worksheet = (WorksheetClass)worksheets[worksheetIndex];
        worksheet.Delete();
        Marshal.ReleaseComObject(worksheet);
    }
    workbook.SaveAs(
        WorkbookPath, _missing, _missing, _missing, _missing, _missing,
        XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
    workbook.Close(true, _missing, _missing);
    application.Quit();
    Marshal.ReleaseComObject(worksheets);
    Marshal.ReleaseComObject(workbook);
    Marshal.ReleaseComObject(application);
}

Что побудило меня задать этот вопрос, так это то, что, будучи преданным LINQ, я действительно хочу сделать что-то вроде этого:

var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name);

... но я обеспокоен тем, что в конечном итоге у меня возникнут утечки памяти или процессы-призраки, если я не отпущу каждый объект рабочего листа (ws).

Любое понимание этого будет оценено.

Обновление

Судя по ответам, похоже, мне действительно нужно выпустить каждый созданный мной ком-объект. Я воспользовался возможностью, чтобы создать класс ComObjectManager, чтобы немного облегчить эту головную боль. Не забывайте использовать метод Get() каждый раз, когда создаете новый объект com, но если вы это сделаете, он позаботится обо всем остальном за вас. Пожалуйста, дайте мне знать, если вы видите какие-либо проблемы с ним (или отредактируйте и оставьте комментарий, если можете). Вот код:

public class ComObjectManager : IDisposable
{
    private Stack<object> _comObjects = new Stack<object>();

    public TComObject Get<TComObject>(Func<TComObject> getter)
    {
        var comObject = getter();
        _comObjects.Push(comObject);
        return comObject;
    }

    public void Dispose()
    {
        // these two lines of code will dispose of any unreferenced COM objects
        GC.Collect();
        GC.WaitForPendingFinalizers();

        while (_comObjects.Count > 0)
            Marshal.ReleaseComObject(_comObjects.Pop());
    }
}

Вот пример использования:

public void CreateExcelWorkbookWithSingleSheet()
{
    using (var com = new ComObjectManager())
    {
        var application = com.Get<ApplicationClass>(() => new ApplicationClass());
        var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing));
        var worksheets = com.Get<Sheets>(() => workbook.Worksheets);
        for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
        {
            var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]);
            worksheet.Delete();
        }
        workbook.SaveAs(
            WorkbookPath, _missing, _missing, _missing, _missing, _missing,
            XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
        workbook.Close(true, _missing, _missing);
        application.Quit();
    }
}

Ответы [ 3 ]

13 голосов
/ 28 мая 2010

Полагаю, вам придется вызывать ReleaseComObject для каждого COM-объекта. Поскольку они не являются сборщиком мусора, иерархия «родитель-потомок» на самом деле не входит в уравнение: даже если вы освобождаете родительский объект, он не уменьшает счетчик ссылок ни на каких дочерних объектах.

6 голосов
/ 28 мая 2010

Вы должны вызывать Marshal.ReleaseComObject для каждого COM-объекта, который вы используете в своем коде, а не только для основного объекта приложения.

2 голосов
/ 28 февраля 2019

Нет. Вам не нужно выпускать ни одного COM-объекта. См. Этот ответ: Очистка объектов взаимодействия Excel с IDisposable

Подведем итог ответа: Сборщик мусора позаботится о них, когда захочет, за исключением случаев, когда ваша программа вылетает. Что вам нужно знать, это:

  1. Запуск вашего приложения в режиме отладки может задержать / предотвратить очистку COM-объект.
  2. Остановка отладчика (из визуальной студии) предотвратить очистку COM-объекта. Это как если бы вы разбили приложение.
  3. Если вы закроете отлаженное приложение должным образом, вы увидите, что все COM-объекты освобождены. Кроме того, запуск вашего приложения в режиме Release и его правильное закрытие также приведет к освобождению всех объектов COM.

Теперь, если вы хотите освободить все COM-объекты сразу после завершения вызова метода, вы можете просто вызвать GC.Collect(); GC.WaitForPendingFinalizers();

Но вам нужно вызывать это ВНЕ метод, который создал COM-объект. Опять же, это может работать не так, как ожидалось, если вы отлаживаете приложение, но оно будет работать в режиме выпуска.

...