Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Warning
Риск взаимоблокировки при использовании async/await и блокирующих вызовов в потоках STA: В управляемом коде (.NET) цикл обработки сообщений STA критически важен для диспетчеризации вызовов COM. Блокировка потока STA с помощью Task.Wait(), Task.Result, Thread.Sleep() или ManualResetEvent.WaitOne() не позволяет завершиться обратным вызовам COM и межапартаментным вызовам, что приводит к взаимоблокировке.
// ❌ DEADLOCK — blocks the STA message loop
[STAThread]
static void Main()
{
var result = GetDataAsync().Result; // Deadlock: awaited continuation
// can't post back to this STA thread
}
// ✅ Correct — use async entry point or pump messages
[STAThread]
static async Task Main()
{
var result = await GetDataAsync(); // continuation resumes on STA via SynchronizationContext
}
Если вы не можете использовать await, используйте CoWaitForMultipleHandles (собственный) или кадр диспетчера или насос сообщений (управляемый) вместо необработанных примитивов ожидания в потоках STA.
Использование однопоточных квартир (процесс модели квартир) предлагает парадигму на основе сообщений для работы с несколькими объектами, работающими одновременно. Это позволяет писать более эффективный код, поскольку, пока один поток ожидает завершения некоторой длительной операции, может выполняться другой поток.
Каждый поток в процессе, который инициализирован как процесс модели квартиры, и который извлекает и отправляет сообщения окна, является потоком с одним потоком квартиры. Каждый поток живет в своей квартире. В пределах апартамента указатели интерфейса могут передаваться без маршалинга, поэтому все объекты в одном потоке однопоточного апартамента взаимодействуют напрямую.
Логическая группа связанных объектов, работающих в одном и том же потоке и поэтому должна выполняться синхронно, может находиться в одном потоке однопотокового апартамента. Однако объект с моделью apartment не может находиться более чем в одном потоке. Вызовы объектов в других потоках должны выполняться в контексте собственного потока, поэтому распределенные com-коммутаторы автоматически передают потоки при вызове прокси-сервера.
Межпроцессные и межпоточные модели похожи. Если необходимо передать указатель интерфейса на объект в другой квартире (в другом потоке) в рамках одного процесса, используется та же модель маршалинга, которую объекты в разных процессах используют для передачи указателей через границы процесса. Получив указатель на стандартный объект маршализации, вы можете выполнять маршализацию указателей интерфейсов через границы потоков выполнения (между апартаментами) так же, как и между процессами. (Указатели интерфейса должны маршалироваться при передаче между квартирами.)
Правила для однопоточных апартаментов просты, но важно внимательно их соблюдать:
- Каждый объект должен существовать только в одном потоке выполнения (в пределах однопоточного апартамента).
- Инициализируйте COM-библиотеку для каждого потока.
- Маршалируйте все указатели на объекты при передаче их между апартаментами.
- Каждый однопоточный апартамент должен иметь цикл сообщений для обработки вызовов из других процессов и апартаментов в рамках одного процесса. Однопоточные апартаменты, не содержащие объектов (только клиентские), также требуют цикла сообщений для обработки широковещательных сообщений, которые используются некоторыми приложениями.
- Объекты на основе DLL или внутрипроцессные объекты не вызывают функции инициализации COM; вместо этого они регистрируют свою модель потоков в именованном значении ThreadingModel в разделе реестра InprocServer32. Объекты, учитывающие модель апартаментов, также должны аккуратно реализовывать точки входа DLL. Существуют особые соображения, касающиеся многопоточности внутрипроцессных серверов. Дополнительные сведения см. в разделе Проблемы многопоточности внутрипроцессного сервера.
Хотя несколько объектов могут существовать в одном потоке, ни один объект апартаментной модели не может существовать более чем в одном потоке.
Каждый поток клиентского процесса или внепроцессного сервера должен вызывать CoInitializeили вызывать CoInitializeEx и указывать COINIT_APARTMENTTHREADED для параметра dwCoInit. Главный апартамент — это поток, который первым вызывает CoInitializeEx. Сведения о внутрипроцессных серверах см. в разделе Проблемы многопоточности внутрипроцессного сервера.
Все вызовы объекта должны выполняться в его потоке (в пределах его апартамента). Запрещено вызывать объект непосредственно из другого потока; использование объектов в этом свободном потоке может вызвать проблемы для приложений. Это правило связано с тем, что все указатели на объекты должны маршалироваться при передаче между квартирами. COM предоставляет следующие две функции для этой цели:
- CoMarshalInterThreadInterfaceInStream упаковывает интерфейс в объект потока данных, который возвращается вызывающей стороне.
- CoGetInterfaceAndReleaseStream отменяет указатель интерфейса из объекта потока и освобождает его.
Эти функции служат оболочкой для вызовов функций CoMarshalInterface и CoUnmarshalInterface, которые требуют использования флага MSHCTX_INPROC.
В общем случае маршалирование выполняется автоматически COM. Например, при передаче указателя интерфейса в качестве параметра в вызове метода прокси-сервера объекту в другой квартире или при вызове CoCreateInstanceCOM выполняет маршалинг автоматически. Однако в некоторых особых случаях, когда разработчик приложения передает указатели на интерфейсы между апартаментами без использования стандартных механизмов COM, он должен выполнять маршалинг вручную.
Если один апартамент (апартамент 1) в рамках процесса имеет указатель интерфейса, а другому апартаменту (апартаменту 2) необходимо использовать этот указатель, апартамент 1 должен вызвать CoMarshalInterThreadInterfaceInStream, чтобы выполнить маршалирование интерфейса. Поток, созданный этой функцией, является потокобезопасным и должен храниться в переменной, доступной апартаменту 2. Апартамент 2 должен передать этот поток в CoGetInterfaceAndReleaseStream, чтобы демаршалировать интерфейс и получить указатель на прокси, через который он сможет получить доступ к интерфейсу. Главный апартамент должен оставаться активным, пока клиент не завершит всю работу с COM (поскольку некоторые внутрипроцессные объекты загружаются в главный апартамент, как описано в разделе Проблемы многопоточности внутрипроцессного сервера). После передачи одного объекта между потоками таким образом очень легко передавать указатели интерфейса в качестве параметров. Таким образом распределенный COM выполняет маршалинг и переключение потоков для приложения.
Для обработки вызовов из других процессов и апартаментов в пределах того же процесса каждый однопоточный апартамент должен иметь цикл обработки сообщений. Это означает, что рабочая функция потока должна иметь цикл GetMessage/DispatchMessage. Если для обмена данными между потоками используются другие примитивы синхронизации, функция MsgWaitForMultipleObjects можно использовать для ожидания сообщений и событий синхронизации потоков. В документации по этой функции приведен пример этого цикла сочетания.
COM создает скрытое окно с помощью класса Windows OleMainThreadWndClass в каждой однопоточной квартире. Вызов объекта принимается этим скрытым окном в виде оконного сообщения. Когда апартамент объекта получает и отправляет сообщение, скрытое окно получит его. Затем процедура окна вызовет соответствующий метод интерфейса объекта.
Когда несколько клиентов вызывают объект, вызовы ставятся в очередь сообщений, и объект будет получать очередной вызов каждый раз, когда его апартамент извлекает и диспетчеризует сообщения. Поскольку вызовы синхронизируются с помощью COM и всегда выполняются потоком, принадлежащим апартаменту объекта, реализациям интерфейсов объекта не требуется обеспечивать синхронизацию. Однопоточные апартаменты могут реализовать IMessageFilter, что позволяет им при необходимости отменять вызовы или получать сообщения окон.
Объект можно повторно ввести, если одна из реализаций метода интерфейса извлекает и отправляет сообщения или выполняет вызов ORPC к другому потоку, что приводит к тому, что другой вызов будет доставлен в объект (по той же квартире). OLE не препятствует повторному входу в одном потоке, но может помочь обеспечить безопасность потока. Это идентично тому, как можно повторно ввести процедуру окна, если она извлекает и отправляет сообщения при обработке сообщения. Однако вызов внепроцессного однопоточного сервера квартиры, который вызывает другой однопоточный сервер квартиры, позволит повторно ввести первый сервер.
Связанные разделы