Как правильно получить доступ к перечислителю COM-объекта DTE2.Windows из другого потока?

У меня есть надстройка Visual Studio, которая использует System.Timers.Timer myTimer.
Каждые N секунд myTimer запускается и выполняет этот код:

foreach(Window window in DTE2.Windows)
{
    TextDocument td = window.Document.Object("TextDocument") as TextDocument;
    // do stuff with td...  
}

Поскольку это вызывается из другого потока, я иногда получаю одну из следующих ошибок:

  • QI для IEnumVARIANT не удалось на неуправляемом сервере.
    в EnvDTE.Windows.GetEnumerator ()
    в строке foreach (окно окна в DTE2.Windows)

  • Приложение вызвало интерфейс, который был упорядочен для другого потока. (Исключение из HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
    в EnvDTE.Window.get_Document ()
    в строке TextDocument td = window.Document.Object ("TextDocument") как TextDocument;

Как правильно получить доступ к этому перечислителю в другом потоке, если задействованы COM-объекты?
Какой-то маршаллинг потока COM?
Что-то еще?


person Mladen Prajdic    schedule 22.12.2010    source источник
comment
Вы хотите сказать, что фрагмент кода вызывается из другого потока каждый раз, когда срабатывает таймер?   -  person Szymon Rozga    schedule 22.12.2010
comment
да. он вызывается в фоновом потоке System.Timers.Timer, как и должно.   -  person Mladen Prajdic    schedule 22.12.2010


Ответы (1)


Вы вступаете в конфликт с COM, пытаясь защитить объектную модель, которая не является потокобезопасной. Таких сложных объектных моделей, как интерфейс автоматизации Visual Studio, никогда не бывает. COM пытается сделать это, автоматически маршалируя вызов, сделанный в фоновом потоке, потоку STA. Это делается через прокси-сервер, реплику исходного COM-интерфейса, который имеет все те же методы, но выполняет маршалинг любых вызовов, сделанных через них, в интерфейс, который запускает методы в потоке STA.

Этот прокси-сервер имеет сходство с потоком, его можно использовать только в потоке, который его создал. DTE2 - вот ваша проблема. Если какой-либо код расширяемости выполнялся ранее и создавал экземпляр интерфейса DTE2, то DTE2 будет «реальным» указателем на интерфейс, а не прокси. Если вы затем используете его в рабочем потоке, таком как тот, который Timer создает для своего события Elapsed, вы получите бомбу. Это работает и наоборот: если DTE2 сначала создается вашим кодом, вы взорвете любой код расширяемости, который работает нормально.

Может, DTE2.DTE решит вашу проблему, не уверен. В конечном итоге это ничего не исправляет, код в любом случае всегда будет выполняться в потоке Visual Studio STA. Просто не используйте System.Timer.Timer, используйте синхронный таймер, например System.Windows.Forms.Timer

person Hans Passant    schedule 22.12.2010
comment
дело в том, что я не хочу использовать System.Windows.Forms.Timer, потому что он блокирует поток графического интерфейса, когда он истекает, и моя операция может быть немного длительной (2-5 секунд). Помогло бы здесь получение нового прокси-сервера IUnknown для DTE? - person Mladen Prajdic; 22.12.2010