Как управлять временем выполнения COM-объектов в стиле RAII в программах на C #?

Моя программа на C # использует компонент COM, который имеет множество различных интерфейсов и подобъектов. Проблема в том, что каждый раз, когда я извлекаю какой-либо COM-интерфейс, создается RCW, и этот RCW существует в течение неизвестного времени (пока не будет собран GC). Каждый RCW содержит некоторый объект на COM-сервере.

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

Пока что я создал общий класс, который реализует IDisposable, принимает ссылку на объект (фактически, RCW) и вызывает _ 2_ в Dispose реализации.

Ради этого вопроса давайте временно проигнорируем возможные проблемы, возникающие при использовании FinalReleaseComObject() - я знаю о них и могу с ними мириться.

Настоящая проблема в том, что я вынужден либо использовать using для всех объектов, либо писать предложения finally и вызывать там Dispose(). Это работает, но код становится довольно загроможденным из-за большого количества дополнительных проводов, и это меня очень беспокоит.

Например, мне нужно присвоить строку свойству someObject.Params.ColorParams.Hint - я не могу просто написать

someObject.Params.ColorParams.Hint = whatever;

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

using( MyWrapper<IParams> params = new MyWrapper<IParams>( someObject.Params ) ) {
    using( MyWrapper<IColorParams> colorParams =
         new MyWrapper<IColorParams>( params.Controlled ) )
    {
        colorParams.Controlled.Hint = whatever;
    }
}

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

Есть ли более элегантное решение проблемы?


person sharptooth    schedule 08.08.2011    source источник
comment
Взгляните на ответ на этот вопрос stackoverflow.com/questions/2191489 /. На самом деле я сам не пробовал, хотя планирую, но это выглядит элегантным решением.   -  person Phil Devaney    schedule 08.08.2011
comment
Оператор using максимально приближен к RAII, который вы собираетесь получить. Вызовите финализаторы, которые вы хотите запустить для выпуска RCW, с помощью GC.Collect + GC.WaitForPendingFinalizers. Если EXE не выходит, найдите все сохраненные ссылки.   -  person Hans Passant    schedule 08.08.2011


Ответы (3)


См. этот ответ Ханса Пассанта по очистке объектов взаимодействия Excel с помощью IDisposable.

Таким образом, вам вообще не нужен IDisposable. Просто явно вызовите сборку мусора после, когда все ваши COM-ссылки вышли из области видимости - то есть не из функции, которая их использует, а из вызывающей стороны этой функции.

person Ian Goldby    schedule 06.10.2016
comment
Заставить сборщик мусора просто избавиться от COM-объектов? Это может отрицательно сказаться на производительности, не так ли? - person sharptooth; 07.10.2016
comment
@sharptooth Насколько я понимаю, в большинстве случаев сборщик мусора позаботится о себе сам, но это тот редкий случай, когда вмешательство оправдано. Меня бы не волновала производительность, если бы я не делал это постоянно в тесном цикле. В любом случае, если Ганс Пассант в данном случае говорит, что все в порядке, значит, все в порядке. :-) - person Ian Goldby; 07.10.2016

Я не уверен, что полностью понимаю проблему, но если я это сделаю (беспорядок кода), есть 2 возможных решения:

Метод-оболочка - вы можете определить простой метод:

TResult Exec<TResult>(Func<TResult> func, params MarshalByRefObject comObjects)
{
  TResult res = default(TResult);
  try
  {
    res = func.Invoke();
  }
  catch (Exception e) { /* Log? */ }
  finally
  {
    foreach (MarshalByRefObject combObj in comObjects)
    {    /* release resources using common interface or reflection */ }
  }
}

Очень похоже на предыдущий метод, только с использованием для этого одного из множества фреймворков АОП (например, PostSharp)

person sternr    schedule 08.08.2011
comment
Я добавил пример - у меня нет объектов одного уровня, у меня есть цепочки подобъектов. - person sharptooth; 08.08.2011

c # и детерминированное высвобождение ресурсов обычно в лучшем случае проблематично.
Освобождение внепроцессных COM-объектов является одним из примеров проблемы.

Если вы счастливы написать код C ++ / cli, он, по крайней мере, имеет возможность семантики стека для управляемых объектов кучи. Это позволяет избежать необходимости использования или окончательной упаковки, поскольку компилятор вызывает dispose, когда объект выпадает из области видимости.

Конечно, это дает вам более шумный язык, чем C #, заголовочные файлы., ->, :: и т. Д.

Как правило, если ваш код может создавать и выпускать COM-объекты в локальной области внутри блока using, я бы остался на C # и смирился с проблемами использования.

Если ваш код должен сохранять COM-объекты в качестве переменных-членов, я бы переместил только эти классы в c ++ / cli и воспользовался семантикой стека для переменных-членов, которые будут автоматически связывать вызовы удаления за вас.

person morechilli    schedule 08.08.2011