Как обрабатываются компоненты 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);
}