C# и Excel — «зависающий» в памяти процесс Microsoft Excel

Достаточно часто при первом знакомстве работы с Excel в C# возникает следующая проблема — после прекращения работы с Excel и завершения работы приложения в фоне остается «висеть» процесс Microsoft Excel. Разберемся с этой проблемой подробнее.

В 99% случаев процесс Excel остается в фоне после завершения работы приложения по причине невнимательности в части освобождения ранее используемых COM-объектов. Чтобы не натыкаться каждый раз на эту проблему, стоит помнить простое правило работы с COM-объектами в C#:

Никогда не использовать две и более точек при работе с COM

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

Рассмотрим пример, когда это правило наглядно продемонстрировано.

Пример «висящего» процесса Excel

Пропустим процесс добавления необходимых ссылок на COM в приложение C# — вся информация по этому вопросу находится в этой статье. Пример следующий: есть книга Excel с одним листом на котором расположена таблица. Нам необходимо считать значения из этой таблицы и вывести их в консоль. Пример таблицы:

Пример таблицы Excel
Пример таблицы Excel

Прочитаем эту таблицу в нашей программе:

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace ExcelLock
{

    internal class Program
    {
        static void Main(string[] args)
        {
            Workbooks workbooks = null;
            Workbook workbook = null;
            Worksheet sheet = null;
            Excel.Range range = null; 
            Excel.Application application = new Excel.Application();
            try
            {
                workbooks = application.Workbooks;//получили доступ к коллекции рабочих книг
                workbook = workbooks.Open(Path.Combine(Directory.GetCurrentDirectory(), "Book.xlsx")); //файл находится рядом с exe-файлом
                sheet = workbook.ActiveSheet;//получили доступ к активному листу книги
                range = sheet.UsedRange;//получили доступ к диапазону занятых ячеек
                var data = range.Value;//прочитали весь диапазон в память

                //читаем строки таблицу, начиная со второй (пропускаем шапку таблицы)
                for (int i = 2; i <= range.Rows.Count; i++) 
                {
                    Console.WriteLine($"Товар {data[i, 1]} Цена за кг {data[i, 2]} Масса {data[i, 3]} Стоимость {data[i, 4]}");
                }
                application.Quit();
            }
            finally
            {
                Marshal.ReleaseComObject(range);
                Marshal.ReleaseComObject(sheet);
                Marshal.ReleaseComObject(workbook);
                Marshal.ReleaseComObject(workbooks);
                Marshal.ReleaseComObject(application);
            }

            
        }
    }
}

На первый взгляд, всё должно работать без проблем — все использованные COM-объекты освобождены в блоке finally, приложение читает таблицу и выводит результаты, но при этом, файл остается занятым процессом Excel, который остается висеть в памяти. Ошибка кроется в цикле for — именно там при забыли про правило использования COM. Смотрим внимательно на описание цикла:

for (int i = 2; i <= range.Rows.Count; i++)

Мы запросили у объекта range еще один объект Rows и уже у Rows запросили количество занятых строк таблицы. В итоге, после завершения работы приложения COM-объект range.Rows оказался не освобожден и процесс Excel «завис» в памяти.

Используем не более одной точки при работе с COM

Чтобы приложение работало корректно, а процесс Excel выгружался из памяти, перепишем наш пример следующим образом:

using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace ExcelLock
{

    internal class Program
    {
        static void Main(string[] args)
        {
            Workbooks workbooks = null;
            Workbook workbook = null;
            Worksheet sheet = null;
            Excel.Range range = null; 
            Excel.Application application = new Excel.Application();
            Excel.Range rows = null; //диапазон занятых строк
            try
            {
                workbooks = application.Workbooks;
                workbook = workbooks.Open(Path.Combine(Directory.GetCurrentDirectory(), "Book.xlsx")); 
                sheet = workbook.ActiveSheet;
                range = sheet.UsedRange;
                rows = range.Rows; //получили диапазон строк
                var data = range.Value;

                //здесь запрашиваем значение у объекта rows
                for (int i = 2; i <= rows.Count; i++) 
                {
                    Console.WriteLine($"Товар {data[i, 1]} Цена за кг {data[i, 2]} Масса {data[i, 3]} Стоимость {data[i, 4]}");
                }
                application.Quit();
            }
            finally
            {
                Marshal.ReleaseComObject(rows); //не забываем освободить объект
                Marshal.ReleaseComObject(range);
                Marshal.ReleaseComObject(sheet);
                Marshal.ReleaseComObject(workbook);
                Marshal.ReleaseComObject(workbooks);
                Marshal.ReleaseComObject(application);
            }

            
        }
    }
}

Здесь мы сохранили объект диапазона строк в переменной Rows и освободили его после использования. В результате, процесс Excel завершается корректно и не остается висеть в памяти.

Итого

При работе с Excel в C# крайне важно следить за тем, какие COM-объекты вы запрашиваете и используете. Правило «одной точки» позволяет избежать ситуации, когда COM-объект не освобождается и процесс Excel остается висеть в памяти, блокирую файл.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии