Интерфейс пользователя + многопоточный апартмент = смерть

Spread the love

Это перевод User interface code + multi-threaded apartment = death. Автор: Реймонд Чен.

Существуют однопоточные апартменты (single-threaded apartment) и многопоточные апартменты (multi-threaded apartment). Ну, сначала существовали только однопоточные… Нет, давайте начнём по-другому.

Сначала в приложениях был только один поток. Вспомните, в 16-битной Windows не было потоков. Каждый процесс, программа и были тем, что сегодня мы называем потоком — тут и сказочке конец. Совместимость с этой древней моделью существует и сегодня — в виде потоковой модели «main». И чем меньше мы про неё будем говорить, тем лучше.

Поэтому поехали дальше: OLE был разработан в те же времена, поэтому он использовал оконные сообщения, чтобы передавать информацию между процессами. В те времена не было никакого иного межпроцессного механизма взаимодействия (IPC). Когда вы инициализировали OLE, он создавал секретное окно OleMainThreadWnd, эти окна использовались для взаимодействия между процессами (а в Win32 — потоками). Как мы узнали ранее, оконные описатели имеют привязку к потоку, что означает, что эти секретные окна для коммуникации также имеют привязку к потоку, что означает, что OLE имеет привязку к потоку. Когда вы делаете вызов объекта из другого апартмента, OLE отправляет сообщение в секретное окно OleMainThreadWnd потока-владельца, сообщая, что нужно сделать объекту, а затем входит в цикл обработки сообщений, ожидая завершения работы (выполняемой потоком-владельцем) и обратной отправки результатов.

В это время команда разработки OLE осознала, что они в действительности делали сразу две вещи. Во-первых, они сделали низкоуровневое управление объектами и интерфейсами (IUnknown, CoMarshalInterThreadInterfaceInStream и т.п.), во-вторых, они сделали высокоуровневое связывание и внедрение объектов (IOleWindow, IOleDocument и т.п.) — что и было изначальной задачей для OLE. Поэтому низкоуровневый слой был отделён в отдельную технологию — COM, а весь высокоуровневый код сохранил имя OLE.

Разделение всего кода на два уровня позволило использовать низкоуровневый код невизуальным программам, которые усердно присматривали себе функциональность по управлению объектами. В результате COM вырастил две «личности»: одна направлена на клиентов GUI, а вторая — на не-GUI. Для не-GUI программ в COM добавили дополнительную функциональность, например, многопоточные апартменты. Поскольку эти программы не обрабатывали GUI, то многопоточные апартменты не были стеснены ограничениями и правилами GUI. Они не отправляли сообщения для взаимодействия друг с другом; они использовали объекты IPC ядра и вызывали WaitForSingleObject. Все только выиграли, или нет?

Ну, да, все выиграли, но вам лучше бы знать, с какой стороны намазан ваш бутерброд. Если вы инициализируете GUI-поток как многопоточный апартмент, то вы нарушаете предположения, для которых многопоточные апартменты были созданы изначально! Многопоточные апартменты предполагают, что вы не запускаете их в GUI-потоках, поскольку эти апартменты не выполняют цикл прокачки оконных сообщений; они просто ждут через WaitForSingleObject. Это не только тормозит широковещательные рассылки, но также блокирует интерфейс вашей программы. Поток, который владеет объектом, может попытаться отправить вашему потоку сообщение, но ваш поток не сможет его обработать, поскольку вместо цикла прокачки сообщений он сидит внутри WaitForSingleObject.

Вот почему COM-объекты, которые связаны с интерфейсом пользователя, почти всегда требуют однопоточный апартмент. И вот почему OleInitialize инициализирует однопоточный апартмент. Потому что многопоточные апартменты были созданы в предположении, что в потоке нет интерфейса пользователя. Как только вы начинаете обрабатывать интерфейс пользователя, вам нужно использовать однопоточный апартмент.

Читать на сайте автора.

0 ответы

Ответить

Хотите присоединиться к обсуждению?
Не стесняйтесь вносить свой вклад!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *