Консольное приложение MTA, вызывающее COM-объект STA из нескольких потоков
Хотя есть много вопросов о COM и STA/MTA (например, здесь), большинство из них говорят о приложениях, которые имеют пользовательский интерфейс. У меня, однако, есть следующие настройки:
- Консольное приложение, которое по умолчанию является многопоточной квартирой (Main(), явно имеет
[MTAThread]
атрибуты). - Основной поток порождает некоторые рабочие потоки.
- Основной поток создает экземпляр однопоточного COM-объекта.
- Основной поток вызывает Console.ReadLine() до тех пор, пока пользователь не нажмет "q", после чего приложение завершится.
Несколько вопросов:
- Многочисленные места упоминают о необходимости прокачки сообщений для COM-объектов. Нужно ли вручную создавать рассылку сообщений для основного потока, или CLR создаст его для меня в новом потоке STA, как предполагает этот вопрос?
- Просто чтобы убедиться - предполагая, что CLR автоматически создает необходимые условия, могу ли я затем использовать COM-объект из любого рабочего потока без необходимости явной синхронизации?
- Что из следующего лучше с точки зрения производительности:
- Пусть CLR позаботится о маршалинге в и из COM-объекта.
- Явно создайте экземпляр объекта в отдельном потоке STA и попросите другой поток связаться с ним, например, с помощью
ConcurrentQueue
,
3 ответа
Да, можно создать COM-объект STA из потока MTA.
В этом случае COM (не CLR) создаст неявную квартиру STA (отдельный поток, принадлежащий COM) или повторно использует существующую, созданную ранее. Там будет создан экземпляр COM-объекта, затем для него будет создан потокобезопасный прокси-объект (COM marshalling wrapper) и возвращен в поток MTA. Все вызовы объекта, сделанные в потоке MTA, будут направляться COM в эту неявную квартиру STA.
Этот сценарий обычно нежелателен. Он имеет много недостатков и может просто не работать должным образом, если COM не может маршалировать некоторые интерфейсы объекта. Проверьте этот вопрос для более подробной информации. Кроме того, петля прокачки сообщений, управляемая неявной квартирой STA, прокачивает только ограниченное количество COM-специфичных сообщений. Это также может повлиять на функциональность COM.
Вы можете попробовать это, и это может хорошо работать для вас. Или вы можете столкнуться с некоторыми неприятными проблемами, такими как тупики, которые довольно сложно диагностировать.
Вот тесно связанный вопрос, на который я только недавно ответил:
StaTaskScheduler и прокачка сообщений потока STA
Я лично предпочел бы вручную управлять логикой вызовов между потоками и привязкой потоков, что-то вроде ThreadAffinityTaskScheduler
предложено в моем ответе.
Вы также можете прочитать это: ИНФОРМАЦИЯ: Описания и работа моделей потоков OLE, настоятельно рекомендуется.
Это делается автоматически COM. Поскольку ваш COM-объект является однопоточным, COM требует подходящего дома для объекта, чтобы обеспечить его использование в поточно-ориентированном виде. Поскольку ваш основной поток недостаточно дружелюбен, чтобы предоставить такие гарантии, COM автоматически создает другой поток и создает объект в этом потоке. Эта нить также автоматически качает, вам ничего не нужно делать, чтобы помочь. Вы можете видеть, как он создается в отладчике. Включите неуправляемую отладку и посмотрите в окне Debug + Windows + Threads. Вы увидите добавление потока, когда переступите через новый вызов.
Красиво и легко, но имеет несколько последствий. Во-первых, компонент COM должен обеспечивать реализацию прокси / заглушки. Вспомогательный код, который знает, как сериализовать аргументы вызова метода, чтобы реальный вызов метода мог быть выполнен в другом потоке. Это обычно предоставляется, но не всегда. Вам будет сложно диагностировать исключение E_NOINTERFACE, если оно отсутствует. Иногда TYPE_E_LIBNOTR номенклатура, распространенная проблема установки.
И что наиболее важно, каждый вызов компонента COM будет маршалинг. Это медленно, маршализованный вызов обычно примерно в 10000 раз медленнее, чем прямой вызов метода, который сам по себе занимает очень мало времени. Как вызов объекта. Это может действительно затормозить вашу программу, конечно.
STA-поток избегает этого и поэтому является рекомендуемым способом использования однопоточного компонента. И да, для потока STA требуется прокачать цикл сообщений. Application.Run() в.NET-программе. Это цикл сообщений, который маршалы вызывают из одного потока в другой в COM. Обратите внимание, что это не обязательно означает, что вы должны иметь цикл сообщений. Если нет необходимости маршалировать вызов или, другими словами, если вы выполняете все вызовы компонента из одного потока, то цикл обработки сообщений не требуется. Обычно это легко гарантировать, особенно в приложении в режиме консоли. Нет, если вы создаете темы сами, конечно.
Еще одна неприятная деталь: однопоточный COM-компонент иногда предполагает, что он создан в потоке, который качает. И будет использовать сам PostMessage(), как правило, когда он использует рабочие потоки внутри и должен вызывать события в потоке STA. Это, конечно, не будет работать правильно, когда вы не качаете. Обычно вы диагностируете это, замечая, что события не инициируются. Типичным примером такого компонента является WebBrowser. Который интенсивно использует потоки внутри, но вызывает события в потоке, в котором он был создан. Вы никогда не получите событие DocumentCompleted, если не будете работать.
Поэтому для получения быстрого быстрого кода может оказаться достаточно поместить [STAThread] в метод Main() даже без вызова Application.Run(). Просто помните о последствиях, видя, что тупик вызова метода или событие не вызывается, является сигнальным признаком того, что прокачка необходима.
Нужно ли вручную создавать рассылку сообщений для основного потока,
Нет. Он находится в MTA, поэтому насос сообщений не требуется.
или CLR создаст его для меня в новой ветке STA
Если COM создает поток (поскольку в процессе нет STA), он также создает насос сообщений (и скрытое окно: его можно увидеть с помощью SPY++ и аналогичных средств отладки).
COM-объект из любого рабочего потока без необходимости явной синхронизации
Зависит.
Если в MTA была создана ссылка на однопоточный объект (STO), то COM предоставит соответствующий прокси. Этот прокси хорош для всех потоков в MTA.
В любом другом случае ссылка должна быть упорядочена, чтобы убедиться, что она имеет правильный прокси.
лучше с точки зрения производительности
Единственный ответ на это - проверить оба и сравнить.
(Помните, что если вы создаете поток для STA, а затем создаете экземпляр объекта локально, вам нужно выполнить накачку сообщений. Мне не ясно, существует ли какой-либо облегченный насос сообщений на уровне CLR, в том числе WinForms только для этого, конечно, нет.)
NB. Единственное подробное объяснение COM и CLR - это .NET и COM: Полное руководство по взаимодействию от Адама Натана (Sams, январь 2002 г.). Но он основан на.NET 1.1 и уже вышел из печати (но есть версия Kindle, доступная через Safari Books Online). Даже эта книга не описывает напрямую, что вы пытаетесь сделать. Я хотел бы предложить некоторые прототипы.