Как обрабатываются компоненты STA COM при использовании в службе WCF, размещенной в IIS (7+)?

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

Что произойдет, если я использую компонент STA COM в службе WCF, размещенной в IIS? Будет ли рабочий процесс иметь цикл сообщений Windows в потоке STA? Могу ли я запустить свой собственный поток STA с собственным циклом сообщений?

2 ответа

Решение

Среда выполнения COM следит за отправкой вызовов методов для COM-объекта внутри STA: вы правы, что это основано на том же механизме ОС, который используется для отправки сообщений Windows, но вам не нужно беспокоиться о том, чтобы это произошло - COM делает это для вас под капотом.

Вам нужно беспокоиться о том, в какой STA будут жить ваши COM-объекты. Если вы создаете экземпляры COM-объектов с использованием квартир с помощью COM-взаимодействия из службы WCF, вам нужно быть осторожным.

Если поток, в котором вы делаете это, не является потоком STA, то все внутрипроцессные COM-объекты будут жить в Host STA по умолчанию для рабочего процесса IIS. Вы не хотите, чтобы это произошло: все ваши COM-объекты для всех сервисных операций окажутся в одной и той же STA. Подсказка кроется в названии - для всех объектов существует только один поток - и все вызовы их методов будут сериализованы, ожидая, пока один и единственный поток в квартире выполнит их. Ваш сервис не будет масштабироваться для обработки нескольких одновременных клиентов.

Вам необходимо убедиться, что COM-объекты, которые вы создаете для обслуживания определенного запроса WCF, находятся в собственной STA отдельно от объектов, созданных для других запросов. Существует два способа сделать это:

  • Раскрути собственную тему, указав ApartmentState.STA в SetApartmentState() перед его запуском, для которого создаются экземпляры COM-объектов для определенного запроса. Этот подход подробно описан Скоттом Сили в ссылке в ответе Кева: он гарантирует, что каждый вызов сервисной операции вызывается в новом инициализированном STA потоке. Более сложным, но более масштабируемым решением в этом направлении было бы реализовать пул повторно используемых STA-инициализированных потоков.
  • Разместите ваши COM-объекты в Приложении COM+, чтобы они жили в отдельном процессе DllHost, где COM+ (через его абстракцию называется Activity) может позаботиться о помещении объектов для разных запросов в разные STA.

Я не совсем уверен, что вы имеете в виду, когда ссылаетесь на обратные вызовы. Возможно, вы имеете в виду вызовы COM-методов для некоторого COM-интерфейса, реализованного в вашем управляемом коде, посредством ссылки, передаваемой COM-объектам в качестве аргумента одному из методов COM-объектов: если это так, это должно просто работать. Но, возможно, вы имеете в виду что-то еще, и в этом случае, возможно, вы могли бы изменить вопрос, чтобы уточнить.

Я обнаружил, что вам нужно качать сообщения в потоке STA в службе WCF, или вы пропускаете обратные вызовы из COM-объекта.

Следующий код работает, но он требует, чтобы вы вызывали COM-объект через Dispatcher.

ComWrapper comWrapper;
Thread localThread;
Dispatcher localThreadDispatcher;

public Constructor()
{
   localThread = new Thread(ThreadProc)
   {
       Name = "test"
   };
   localThread.SetApartmentState(ApartmentState.STA);

   AutoResetEvent init = new AutoResetEvent(false);

   localThread.Start(init);

   init.WaitOne();
}

private void ThreadProc(object o)
{
    localThreadDispatcher = Dispatcher.CurrentDispatcher;
    ((AutoResetEvent)o).Set();

    comWrapper = new ComWrapper()

    Dispatcher.Run();

    localThreadFinished.Set();
 }

А затем совершайте звонки следующим образом.

public void UsefulComOperation()
{
    localThreadDispatcher.Invoke(new Action( () => comWrapper.UsefulOperation);
}
Другие вопросы по тегам