В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда после завершения работы с приложением Microsoft Excel через механизмы COM-автоматизации попытка удалить временный файл приводит к ошибке: "Ошибка совместного доступа к файлу". Это происходит потому, что операционная система не успевает мгновенно завершить процесс excel.exe и освободить дескриптор файла, даже если была вызвана команда Quit(). В этой статье мы подробно разберем, почему это происходит и как правильно организовать код, чтобы избежать подобных блокировок.
Разберем ситуацию: когда мы инициализируем объект COMОбъект("Excel.Application"), операционная система запускает отдельный процесс. Метод ОбъектExcel.Application.Quit() посылает приложению Excel команду на закрытие. Однако выполнение этой команды в самом Excel происходит асинхронно по отношению к коду 1С. Программа 1С продолжает выполнение следующей строки кода (удаление файла) в то время, как Excel еще может записывать данные в профиль пользователя или закрывать внутренние потоки. Рассмотрим, какие стратегии помогут нам решить эту задачу.
Это наиболее простой и часто используемый на практике метод. Если файл занят другим процессом, мы не останавливаем выполнение программы критической ошибкой, а пробуем повторить операцию через небольшие промежутки времени. Проанализируем пример кода, который позволяет дождаться освобождения файла:
ФайлНеУдален = Истина;
КоличествоПопыток = 0;
МаксимальноеКоличествоПопыток = 254; // Ограничиваем цикл, чтобы избежать бесконечного зависания
Пока ФайлНеУдален И (КоличествоПопыток < МаксимальноеКоличествоПопыток) Цикл
Попытка
УдалитьФайлы(врИмяФайла);
ФайлНеУдален = Ложь;
Исключение
КоличествоПопыток = КоличествоПопыток + 1;
// Можно добавить небольшую паузу, если это необходимо
КонецПопытки;
КонецЦикла;
Как показывает практика, файл обычно освобождается уже на 10–20 итерации, что занимает доли секунды. Этот метод хорош своей надежностью, но стоит помнить о замечании из обсуждения: если данный код выполняется внутри транзакции, то возникновение исключения в блоке Попытка...Исключение может пометить транзакцию как "битую", и последующее выполнение ЗафиксироватьТранзакцию() вызовет ошибку.
Одной из скрытых причин удержания процесса excel.exe в памяти является наличие живой ссылки на объект в самой 1С. Даже если вы вызвали Quit(), переменная ОбъектExcel все еще содержит ссылку на COM-объект. Пока переменная не будет очищена, 1С не "отпустит" Excel окончательно. Рассмотрим правильную последовательность действий:
Workbook.Close().Application.Quit().Посмотрим на пример правильного завершения работы:
// Отключаем диалоговые окна
ОбъектExcel.DisplayAlerts = Ложь;
// Закрываем приложение
ОбъектExcel.Quit();
// Принудительно освобождаем ссылку в 1С
ОбъектExcel = Неопределено;
// Теперь можно пробовать удалять файл
УдалитьФайлы(врИмяФайла);
Установка значения Неопределено уничтожает COM-объект в памяти 1С, что посылает сигнал операционной системе о том, что клиент завершил работу с сервером автоматизации.
Если мы работаем на сервере в контексте управляемых форм, использование метода Приостановить() (которого нет в стандартном наборе функций для сервера) недоступно. Однако мы можем реализовать паузу другими способами. Рассмотрим вариант с использованием объекта WScript.Shell, который позволяет сделать паузу в миллисекундах:
Процедура ПаузаНаСервере(Секунд)
Скрипт = Новый COMОбъект("WScript.Shell");
Скрипт.Run("ping 127.0.0.1 -n " + Формат(Секунд + 1, "ЧГ=0"), 0, Истина);
КонецПроцедуры
Использование команды ping — это классический трюк для создания задержки в серверном коде. Параметр -n указывает количество запросов, а флаг Истина в методе Run заставляет 1С ждать завершения выполнения команды. Это позволяет дать Excel время на "честное" закрытие всех дескрипторов.
Выясним, всегда ли нам нужен Excel как COM-объект. Если ваша задача — просто сформировать файл и сохранить его на диск, или прочитать данные из файла .xlsx, платформа 1С предоставляет гораздо более эффективные встроенные средства. Использование объекта ТабличныйДокумент позволяет работать с форматами Excel напрямую, без запуска тяжелого процесса excel.exe.
Проанализируем пример сохранения без использования COM:
ТабДок = Новый ТабличныйДокумент;
// ... заполнение документа данными ...
ТабДок.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.XLSX);
Этот метод полностью снимает проблему блокировки файлов, так как запись производится средствами самой платформы. Это работает быстрее, стабильнее и не требует установленного Microsoft Office на сервере.
Иногда проблема кроется не в коде, а в окружении. Проанализируем системные факторы:
USR1CV8), имеет полные права на папку с временными файлами.Quit(), процесс Excel может остаться "висеть" в памяти. Рекомендуется использовать конструкцию Попытка...Исключение...КонецПопытки, чтобы закрытие файла происходило в любом случае, даже при возникновении ошибок в логике обработки.Подводя итог, можно сказать, что наиболее надежным способом удаления файла является сочетание правильного освобождения переменной ОбъектExcel = Неопределено; и небольшого цикла попыток удаления с использованием блока Попытка. Это гарантирует стабильную работу системы даже при высокой нагрузке на дисковую подсистему сервера.