1C V83 COM Connector: Как правильно копировать данные и оптимизировать асинхронный цикл в .NET

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции систем на платформе .NET с 1С через COM Connector разработчики часто сталкиваются с неочевидным поведением объектов. Типичная ситуация: вы запускаете цикл перебора данных из запроса, передаете их в асинхронную очередь (например, через Channel), но на выходе получаете набор абсолютно одинаковых записей — последних в выборке. Разберем подробно, почему это происходит, как правильно «клонировать» данные и почему стандартный подход с COM-объектами работает так медленно.

Разбор проблемы: Почему данные в цикле «дублируются»

Рассмотрим ситуацию, описанную автором темы. При использовании метода QueryResultSelection.Next() (в коде C# это data.Next()) мы работаем с однонаправленным курсором. Объект выборки — это не массив данных, а указатель на текущую строку. Когда вы вызываете new OneWithObject(data) и сохраняете ссылку на COM-объект внутри своего класса, вы сохраняете ссылку на итератор, а не на конкретные данные строки.

В асинхронном цикле происходит следующее:

  1. Цикл чтения быстро пробегает по всем записям, вызывая Next().
  2. На каждой итерации в очередь Channel уходит ссылка на один и тот же COM-объект.
  3. К моменту, когда поток-обработчик начинает читать данные из очереди, основной цикл уже дошел до конца выборки.
  4. В итоге все объекты в очереди «смотрят» на последнюю запись в 1С.

Важное правило: COM-объекты 1С являются ссылочными типами. Чтобы сохранить состояние строки, необходимо либо выгрузить данные в структуру, не зависящую от состояния курсора, либо копировать значения полей в примитивные типы .NET (string, int, double) непосредственно в момент итерации.

Решение №1: Использование Таблицы Значений (ValueTable)

Проанализируем самый простой и эффективный способ избежать проблем с курсором — использование метода Unload() (Выгрузить). Вместо того чтобы перебирать выборку (QueryResultSelection), мы выгружаем результат запроса в таблицу значений, которая представляет собой коллекцию строк в памяти.


// На стороне C# (через COM)
var queryResult = queryObject.Execute();
var table = queryResult.Unload(); // Получаем объект ValueTable
var count = table.Count();

for (int i = 0; i < count; i++)
{
    var row = table.Get(i); // Получаем конкретную строку по индексу
    // Теперь row ссылается на конкретную область памяти в 1С, 
    // которая не изменится при переходе к следующему i
    await channel.Writer.WriteAsync(new OneWithObject(row));
}

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

Проблема английских имен свойств (Code, Description, Ref)

Выясним причину, по которой после выгрузки в ValueTable могут перестать быть доступны английские синонимы свойств (например, Description вместо Наименование). В 1С при работе через COM доступность имен зависит от того, как сформирована таблица.

В объекте выборки (Selection) платформа часто предоставляет доступ к обоим языкам через механизмы IDispatch. Однако в ValueTable имена колонок строго соответствуют именам, заданным в запросе или структуре таблицы. Чтобы гарантированно получать данные по нужным именам, используйте псевдонимы в тексте запроса:


ВЫБРАТЬ
    МестаХранения.Ссылка КАК Ref,
    МестаХранения.Код КАК Code,
    МестаХранения.Наименование КАК Description
ИЗ
    Справочник.МестаХраненияДел КАК МестаХранения

В этом случае в объекте row (строка таблицы) колонки будут называться именно так, как указано в блоке КАК (AS), и ошибка Exception при обращении к английским именам исчезнет.

Решение №2: Оптимизация через JSON-сериализацию

Рассмотрим ситуацию с низкой производительностью (1–4 объекта в секунду). Основная причина тормозов — это маршалинг и STA-модель COM. Каждый вызов метода или свойства через точку (например, row.Code) — это отдельный межпроцессный вызов, который требует переключения контекста потока. Если у вас 4000 объектов и у каждого по 10 свойств, вы совершаете 40 000 тяжелых вызовов.

Проанализируем самый быстрый архитектурный подход: передача данных одной строкой. Вместо того чтобы тянуть каждый объект по отдельности, мы заставляем 1С упаковать всё в JSON.

  1. На стороне 1С пишется функция, которая выполняет запрос и преобразует ValueTable в JSON-строку.
  2. В C# через COM вызывается эта функция.
  3. Полученная строка десериализуется средствами System.Text.Json или Newtonsoft.Json.

Пример кода на стороне 1С (концептуально):


ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ТаблицаРезультатов);
Возврат ЗаписьJSON.Закрыть();

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

Важные аспекты управления памятью: ReleaseComObject

Разберем критическую ошибку, которая часто приводит к «раздуванию» памяти (Memory Leak). В .NET каждый COM-объект оборачивается в RCW (Runtime Callable Wrapper). Сборщик мусора (GC) не знает, сколько памяти занимает реальный объект внутри процесса 1С, и может не очищать RCW вовремя.

При итерации тысяч объектов необходимо вручную освобождать ресурсы. Рассмотрим правильный цикл:


while (data.Next())
{
    var code = data.Code; // Получаем данные
    // Обрабатываем данные...
    
    // Если мы получили объект (например, Ссылка), его нужно освободить
    // Marshal.ReleaseComObject(data.Ref);
}

Рекомендация: Если вы все же используете COM-перебор, старайтесь сохранять данные в POCO-классы C# (обычные объекты) как можно быстрее, а ссылки на COM-объекты уничтожать через Marshal.ReleaseComObject() сразу после использования.

Выводы и рекомендации

Подведем итог нашему анализу работы с COM Connector:

Соблюдение этих структурных правил позволит вам создавать надежные и быстрые интеграционные решения между 1С и платформой .NET.

← На главную