Excel 2007 зависает при закрытии через.NET

У меня есть программа Visual Basic .NET, которая должна открывать и закрывать электронную таблицу Excel. Открытие и чтение электронной таблицы работают нормально, но попытка закрыть приложение Excel 2007 приводит к зависанию. Кажется, закрывается, но если вы посмотрите в диспетчере задач, приложение все еще работает. Код, который я использую, чтобы закрыть его

wbkData.Close(saveChanges:=False)
appExcel.Quit()
wbkData = Nothing
appExcel = Nothing

Как я могу заставить Excel закрыться правильно?

3 ответа

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

Я не могу видеть из вашего примера кода, но в основном всегда назначаю ваши объекты Excel локальным переменным, никогда не опускаясь "на две точки", например так:

//FAIL

Workbook wkBook = xlApp.Workbooks.Open(@"C:\mybook.xls");

вместо ссылки каждый объект в отдельности:

//WIN

Worksheets sheets = xlApp.Worksheets;
Worksheet sheet = sheets.Open(@"C:\mybook.xls");
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

.NET создает оболочку для COM-объекта, которая невидима для вас и не освобождается до тех пор, пока GC не сотворит свою магию.

До тех пор, пока я не обнаружил это, я запускал приведенный ниже хакерский код в приложении ASP.NET каждый раз, когда создавал новую книгу, в которой проверяется возраст процесса excel.exe и убиваются все, которые старше минуты:

//force kill any excel processes over one minute old.
try
{
    Process[] procs = Process.GetProcessesByName("EXCEL");
    foreach (Process p in procs)
    {
        if (p.StartTime.AddMinutes(1) < DateTime.Now)
        {
            p.Kill(); 
        }  
    }  
}
catch (Exception)
{}  

Я написал этот пост, который вы упомянули в блоге команды Excel...

Я также обсуждал эту проблему ранее в Stackru для вопроса Как правильно очистить объекты взаимодействия Excel в C#.

Первый ответ на этот вопрос был помечен как "правильный" и набрал 11 голосов, но я вас уверяю, что эту политику крайне сложно правильно использовать на практике. Если кто-то когда-нибудь проскальзывает и использует "две точки", или итерирует ячейки через цикл для каждого цикла, или любой другой подобный тип команды, то у вас будут COM-объекты без ссылок и вы рискуете зависнуть - и не будет никакого способа найти причину этого в коде.

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

Я нашел решение в блоге MSDN Excel, который работал для меня. Это объясняется как

Есть две проблемы с вышеуказанным:

(1) Несмотря на то, что код, по-видимому, сначала удаляет объект 'wbkData' и т. Д., Приведенный выше код на самом деле не реализует это, поскольку процедура сборки мусора.NET может удалять свои объекты в любом порядке. (GC является недетерминированным по порядку, а не просто недетерминированным по времени.)

(2) Такие команды, как 'wsh = wbkData.Workssheets.Item(1)' - или подобные строки - очень распространены и создают объект RCW, обертывающий объект "Рабочие листы". У вас не будет переменной, содержащей ссылку на нее, поэтому вы обычно об этом не думаете, но этот объект RCW не будет утилизироваться до следующей сборки мусора. Однако приведенный выше код вызывает GC.Collect() последним, и поэтому RCW по-прежнему содержит ссылку на этот объект "Рабочие листы" при вызове appExcel.Quit(). Excel зависает в результате.

Финальный код выглядит так

GC.Collect()
GC.WaitForPendingFinalizers()

wbkData.Close(SaveChanges:=False)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(wbkData) : wbkData = Nothing
appExcel.Quit()
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(appExcel) : appExcel = Nothing
Другие вопросы по тегам