ApartmentState для чайников

Я только что исправил ошибку, используя это:

_Thread.SetApartmentState(ApartmentState.STA);

Теперь я хотел бы понять, что это значит и почему это работает!

Ответ 1

COM является прародителем .NET. У них были довольно высокие цели, одна из вещей, которую делает COM, но .NET полностью пропускает, предоставляет гарантии многопоточности для класса. Класс COM может публиковать, какие требования к потокам он имеет. И инфраструктура COM обеспечивает соответствие этим требованиям.

Это полностью отсутствует в .NET. Вы можете использовать объект Queue <>, например, в нескольких потоках, но если вы не заблокируете должным образом, в вашем коде будет неприятная ошибка, которую очень сложно диагностировать.

Точная информация о потоке COM слишком велика, чтобы поместиться в сообщении. Я сосредоточусь на специфике вашего вопроса. Поток, который создает COM-объекты, должен сообщить COM, какую поддержку он хочет оказать COM-классам с ограниченными параметрами потоков. Подавляющее большинство из этих классов поддерживают только так называемые многоуровневые потоки, их методы интерфейса можно безопасно вызывать только из того же потока, который создал экземпляр. Другими словами, они объявляют: "Я не поддерживаю потоки, пожалуйста, позаботьтесь о том, чтобы никогда не вызывать меня из неправильной темы". Даже если клиентский код действительно вызывает его из другого потока.

Есть два вида, STA (однопоточные квартиры) и MTA. Это указывается в вызове CoInitializeEx(), функции, которая должна вызываться любым потоком, который делает что-либо с COM. CLR выполняет этот вызов автоматически всякий раз, когда запускает поток. Для основного потока запуска вашей программы он получает значение, передаваемое из атрибута [STAThread] или [MTAThread] вашего метода Main(). По умолчанию это MTA. Для потоков, которые вы создаете сами, это определяется вашим вызовом SetApartmentState(). По умолчанию это MTA. Потоки потоков всегда MTA, которые не могут быть изменены.

В Windows много кода, требующего STA. Примечательные примеры, которые вы бы использовали сами, - это буфер обмена, перетаскивание + падение и диалоги оболочки (например, OpenFileDialog). И много кода, который вы не видите, например, программы автоматизации пользовательского интерфейса и хуки для наблюдения за сообщениями. Ни один из этого кода не должен быть потокобезопасным, его автору было бы очень трудно сделать его безопасным, не зная, в какой программе он используется. Соответственно поток UI проекта WPF или Windows Forms всегда должен быть STA для поддержки такого кода, как и любой поток, который создает окно.

Однако обещание, которое вы даете COM, что ваша нить - STA, требует от вас выполнения однопотокового договора на квартиру. Они довольно жесткие, и вам может быть сложно диагностировать проблемы, когда вы нарушаете контракт. Требования заключаются в том, что вы никогда не блокируете поток в течение какого-либо промежутка времени и что вы прокачиваете цикл сообщений. Последнее требование выполняется потоком пользовательского интерфейса WPF или Winforms, но вам придется позаботиться об этом самостоятельно, если вы создадите свой собственный поток STA. Общая диагностика нарушения контракта - тупик.

Между прочим, в CLR встроена поддержка поддержки этих требований, что поможет вам избежать неприятностей. Оператор lock и методы WaitOne() прокачивают цикл сообщений, когда он блокирует поток STA. Это, однако, заботится только о требовании никогда не блокировать, вам все равно нужно создать свой собственный цикл сообщений. Application.Run() как в WPF, так и в Winforms.

Ранее я предоставил ответ, который содержит более подробную информацию о значении наличия цикла сообщений, чтобы поддерживать COM счастливым. Вы найдете этот пост здесь.