Как правильно очистить объекты взаимодействия Excel? - PullRequest
714 голосов
/ 01 октября 2008

Я использую взаимодействие Excel в C # (ApplicationClass) и поместил следующий код в мое предложение finally:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

Хотя этот вид работает, процесс Excel.exe остается в фоновом режиме даже после закрытия Excel. Он открывается только после того, как мое приложение закрыто вручную.

Что я делаю не так, или есть ли альтернатива для обеспечения правильного удаления объектов взаимодействия?

Ответы [ 39 ]

4 голосов
/ 18 июня 2011

Когда все вышеперечисленное не работает, попробуйте дать Excel немного времени, чтобы закрыть его листы:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);
3 голосов
/ 08 августа 2012

Убедитесь, что вы освободили все объекты, связанные с Excel!

Я провел несколько часов, пытаясь несколькими способами. Все это отличные идеи, но я наконец-то нашел свою ошибку: Если вы не отпустите все объекты, ни один из вышеперечисленных способов не поможет вам , как в моем случае. Убедитесь, что вы отпустили все объекты, включая дальний!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

Опции вместе здесь .

2 голосов
/ 13 декабря 2013

Правило двух точек у меня не сработало. В моем случае я создал метод для очистки моих ресурсов следующим образом:

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}
2 голосов
/ 11 января 2013

Отличная статья по освобождению COM-объектов: 2.5. Выпуск COM-объектов (MSDN).

Метод, который я бы рекомендовал, состоит в том, чтобы обнулить ваши ссылки Excel.Interop, если они не являются локальными переменными, а затем дважды вызвать GC.Collect() и GC.WaitForPendingFinalizers(). Переменные взаимодействия с локальной областью видимости будут обрабатываться автоматически.

Это устраняет необходимость сохранять именованную ссылку для каждого COM-объекта.

Вот пример, взятый из статьи:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

Эти слова прямо из статьи:

Практически во всех ситуациях обнуление ссылки на RCW и принудительная сборка мусора приведут к корректной очистке. Если вы также вызовете GC.WaitForPendingFinalizers, сборка мусора будет настолько детерминированной, насколько вы сможете это сделать. То есть вы будете точно уверены, когда объект будет очищен - при возврате из второго вызова WaitForPendingFinalizers. В качестве альтернативы вы можете использовать Marshal.ReleaseComObject. Однако учтите, что вам вряд ли когда-нибудь понадобится использовать этот метод.

2 голосов
/ 14 марта 2012

Вы должны быть очень осторожны, используя приложения взаимодействия Word / Excel. После того, как мы опробовали все решения, у нас все еще оставалось много процессов "WinWord", открытых на сервере (с более чем 2000 пользователей).

После долгих часов работы над проблемой я понял, что, если я одновременно открываю более двух документов, используя Word.ApplicationClass.Document.Open() в разных потоках, рабочий процесс IIS (w3wp.exe) завершится с ошибкой, и все процессы WinWord будут открыты!

Так что я думаю, что нет абсолютного решения этой проблемы, но переход на другие методы, такие как Office Open XML разработка.

1 голос
/ 10 марта 2017

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

1: убедитесь, что нет ссылок на созданное вами приложение Excel (у вас должно быть только одно; установите его в null)

2: вызов GC.Collect ()

3: Excel должен быть закрыт либо пользователем, вручную закрывающим программу, либо вызовом Quit для объекта Excel. (Обратите внимание, что Quit будет работать так же, как если бы пользователь пытался закрыть программу, и отобразит диалоговое окно подтверждения, если есть несохраненные изменения, даже если Excel не виден. Пользователь может нажать «Отмена», после чего Excel не будет закрыт. .)

1 должно произойти раньше, чем 2, но 3 может произойти в любое время.

Один из способов реализовать это состоит в том, чтобы обернуть объект Excel взаимодействия с вашим собственным классом, создать экземпляр взаимодействия в конструкторе и реализовать IDisposable с Dispose, похожим на

Это очистит Excel от проблем вашей программы. После закрытия Excel (вручную пользователем или вызовом Quit) процесс исчезнет. Если программа уже была закрыта, процесс исчезнет при вызове GC.Collect ().

(Я не уверен, насколько это важно, но вы можете захотеть вызвать GC.WaitForPendingFinalizers () после вызова GC.Collect (), но не обязательно избавляться от процесса Excel.)

Это работало для меня без проблем в течение многих лет. Имейте в виду, что хотя это работает, вам действительно нужно изящно закрыться, чтобы это работало. Вы по-прежнему будете накапливать процессы Excel.exe, если прервете свою программу до очистки Excel (обычно нажимая «stop» во время отладки вашей программы). '

1 голос
/ 02 августа 2016

В настоящее время я работаю над автоматизацией Office и наткнулся на решение для этого, которое работает каждый раз для меня. Это просто и не требует уничтожения каких-либо процессов.

Похоже, что при простом цикле текущих активных процессов и при любом доступе к открытому процессу Excel любой случайный зависший экземпляр Excel будет удален. Приведенный ниже код просто проверяет наличие процессов с именем «Excel», а затем записывает свойство MainWindowTitle процесса в строку. Такое «взаимодействие» с процессом, похоже, заставляет Windows догнать и прервать замороженный экземпляр Excel.

Я запускаю приведенный ниже метод как раз перед тем, как надстройка, которую я разрабатываю, завершает работу, поскольку она запускает событие выгрузки. Он удаляет все зависшие экземпляры Excel каждый раз. Честно говоря, я не совсем уверен, почему это работает, но он работает хорошо для меня и может быть помещен в конец любого приложения Excel, не беспокоясь ни о двойных точках, Marshal.ReleaseComObject, ни о процессах уничтожения. Я был бы очень заинтересован в любых предложениях относительно того, почему это эффективно.

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}
1 голос
/ 01 октября 2008

Я думаю, что отчасти это то, как фреймворк обрабатывает приложения Office, но я могу ошибаться. В некоторые дни некоторые приложения очищают процессы сразу, а в другие дни кажется, что они ждут закрытия приложения. В общем, я перестал обращать внимание на детали и просто следил за тем, чтобы в конце дня не было никаких дополнительных процессов.

Кроме того, и, возможно, я слишком упрощаю вещи, но я думаю, что вы можете просто ...

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

Как я уже говорил ранее, я не склонен обращать внимание на детали того, когда процесс Excel появляется или исчезает, но это обычно работает для меня. Мне также не нравится держать процессы Excel в течение какого-то иного периода времени, кроме минимального, но я, вероятно, просто параноик по этому поводу.

1 голос
/ 05 сентября 2014

Принятый ответ не работает для меня. Следующий код в деструкторе сделал свою работу.

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
1 голос
/ 18 июня 2014

Мое решение

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

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}
...