Приложение консоли MTA, вызывающее объект STA COM из нескольких потоков

Хотя есть много вопросов о COM и STA/MTA (например, здесь), большинство из них говорит о приложениях с пользовательским интерфейсом. У меня, однако, есть следующая настройка:

  • Консольное приложение, по умолчанию Multi-Threaded Apartment (Main() явно имеет атрибут [MTAThread]).
  • Основной поток генерирует некоторые рабочие потоки.
  • Основной поток создает однопоточный COM-объект.
  • Основной поток вызывает Console.ReadLine(), пока пользователь не нажмет "q", после чего приложение завершится.

Несколько вопросов:

  • В многочисленных местах упоминается потребность в насосе сообщений для объектов COM. Мне нужно вручную создать сообщение-насос для основного потока, или CLR создаст его для меня в новом потоке STA, как предлагает этот вопрос?
  • Чтобы убедиться, что CLR автоматически создает необходимую сантехнику, могу ли я затем использовать COM-объект из любого рабочего потока без необходимости явной синхронизации?
  • Что из следующего лучше с точки зрения производительности:
    • Пусть CLR позаботится о маршалинге и из COM-объекта.
    • Явным образом создайте экземпляр объекта в отдельном потоке STA и передайте ему другой поток через, например, a ConcurrentQueue.

Ответ 1

Да, можно создать объект STA COM из потока MTA.

В этом случае COM (не CLR) создаст неявную квартиру STA (отдельный поток, принадлежащий COM) или повторно использует существующий, созданный ealier. Объект COM будет создан там, тогда для него будет создан потокобезопасный прокси-объект (COM-маршаллинг-обертка), который будет возвращен в поток MTA. Все вызовы объекта, созданного в потоке MTA, будут объединены COM в эту неявную квартиру STA.

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

Вы можете попробовать, и это может сработать для вас. Или вы можете столкнуться с некоторыми неприятными проблемами, такими как взаимоблокировки, довольно трудно диагностировать.

Вот близкий вопрос, который я недавно ответил:

Перекачка сообщений StaTaskScheduler и STA

Я лично предпочел бы вручную управлять логикой межпоточных вызовов и аффинности потоков, с чем-то вроде ThreadAffinityTaskScheduler. в моем ответе.

Вы также можете прочитать следующее: INFO: описания и разработки моделей потоков OLE, настоятельно рекомендуется.

Ответ 2

Это выполняется автоматически с помощью COM. Поскольку ваш объект COM является однопоточным, COM требует подходящего дома для объекта, чтобы гарантировать, что он используется поточно-безопасным способом. Поскольку ваш основной поток недостаточно дружелюбен, чтобы обеспечить такие гарантии, COM автоматически создает другой поток и создает объект в этом потоке. Эта нить также автоматически накачивает, вам нечего делать, чтобы помочь. Вы можете видеть, что он создается в отладчике. Включите неуправляемую отладку и посмотрите в окне Debug + Windows + Threads. Вы увидите добавление потока при переходе через новый вызов.

Приятный и легкий, но он имеет несколько последствий. Во-первых, COM-компонент должен обеспечить реализацию прокси/заглушки. Вспомогательный код, который знает, как сериализовать аргументы вызова метода, поэтому вызов реального метода может быть выполнен в другом потоке. Это обычно предоставляется, но не всегда. Вам будет сложно диагностировать исключение E_NOINTERFACE, если оно отсутствует. Иногда TYPE_E_LIBNOTREGISTERED - общая проблема с установкой.

И самое главное, каждый вызов COM-компонента будет маршалирован. Этот медленный, маршалированный вызов обычно примерно на 10 000 раз медленнее, чем прямой вызов метода, который сам занимает очень мало времени. Как звонок getter свойства. Конечно, это действительно может испортить вашу программу.

Поток STA позволяет избежать этого, и поэтому рекомендуется использовать однопоточный компонент. И да, это требование для потока STA для накачки контура сообщения. Application.Run() в .NET-программе. Это цикл сообщений, который маршалы звонят из одного потока в другой в COM. Помните, что это не обязательно означает, что у вас должен быть цикл сообщений. Если вызов никогда не требуется для маршалинга или, другими словами, если вы делаете все вызовы компонента из одного потока, тогда цикл сообщения не нужен. Это обычно легко гарантировать, особенно в консольном режиме. Нет, если вы сами создаете темы.

Еще одна неприятная деталь: однопоточный COM-компонент иногда предполагает, что он создан на потоке, который накачивает. И будет использовать сам PostMessage(), как правило, когда он использует внутренние рабочие потоки и должен создавать события в потоке STA. Это, конечно, не будет работать правильно, когда вы не будете качать. Вы обычно диагностируете это, замечая, что события не поднимаются. Общим примером такого компонента является WebBrowser. Что сильно использует потоки внутри, но вызывает события в потоке, на котором он был создан. Вы никогда не получите событие DocumentCompleted, если вы не откачиваете.

Так что ставить [STAThread] на ваш метод Main() может быть достаточно, чтобы получить счастливый быстрый код, даже без обращения к Application.Run(). Просто следите за последствиями, наблюдая, как тупик вызова метода или событие, которое не поднимается, является контрольным знаком, что требуется перекачка.

Ответ 3

Мне нужно вручную создать сообщение-насос для основного потока,

Нет. Поэтому в MTA нет необходимости в насосе сообщений.

или CLR создаст его для меня в новом потоке STA

Если COM создает поток (потому что в этом процессе нет STA), он также создает насос сообщений (и скрытое окно: можно увидеть с помощью инструментов SPY ++ и аналогичных средств отладки).

COM-объект из любого рабочего потока без необходимости явной синхронизации

Зависит.

Если ссылка на однопоточный объект (STO) была создана в MTA, то COM предоставит соответствующий прокси. Этот прокси-сервер хорош для всех потоков в MTA.

В любом другом случае ссылка должна быть настроена для обеспечения правильного прокси-сервера.

лучше с точки зрения производительности

Единственный ответ на это - проверить оба и сравнить.

(Помните, что если вы создаете поток для STA, а затем создаете экземпляр объекта локально, вам нужно выполнить перекачку сообщения. Мне не ясно, что есть какой-либо легкий поток сообщений CLR-уровня, включая WinForms, просто для этого, конечно, isn "т.)

NB. Единственным углубленным объяснением COM и CLR является .NET и COM: полное руководство по совместимости Адама Натана (Sams, January 2002). Но он основан на .NET 1.1 и теперь выходит из печати (но есть версия Kindle и доступна через Safari Books Online). Даже в этой книге не описывается непосредственно то, что вы пытаетесь сделать. Я бы предложил несколько прототипов.