Исходящий вызов не может быть выполнен, так как приложение отправляет синхронный входной вызов - PullRequest
13 голосов
/ 12 января 2012

Я получил это (ошибка в названии выше) из пула потоков System.Thread.Timer, поэтому у меня есть TimerWrapper, который оборачивает System.Thread.Timer для перемещения фактического выполнения в System.Thread.ThreadPool, и я до сих порполучить его, поэтому я переместить его новый поток (обратный вызов) .Start (), и я все еще получаю его.Как происходит отправка входного синхронного вызова, когда я помещаю его в совершенно новый поток ???

Это очень очень маленькое приложение-прототип, в котором все, что я делаю, - это запускаю таймер, который делает это...

    IEnumerable swc = SHDocVw.ShellWindows() 
    HashSet<WindowInfo> windows = new HashSet<WindowInfo>();
    foreach (SHDocVw.InternetExplorer ie in swc)
    {
        if (!ie.FullName.ToLower().Contains("iexplore.exe"))
            continue;

        IntPtr hwnd;
        IEPlugin.IOleWindow window = ie.Document as IEPlugin.IOleWindow;
        window.GetWindow(out hwnd);   

        WindowInfo info = new WindowInfo();
        info.handle = hwnd;
        info.extraInfo = ie;
        windows.Add(info);
    }

1 Ответ

34 голосов
/ 13 января 2012

Поздравляем;вам удалось наткнуться на одну из моих любимых причуд COM, в данном случае восхитительно неясное ограничение с помощью метода GetWindow IOleWindow - и сообщение об ошибке, которое дает вам небольшое представление о том, что происходит.Основная проблема здесь в том, что метод GetWindow() помечен как [input_sync] - из файла include \ oleidl.idl в SDK:

interface IOleWindow : IUnknown
{
...
    [input_sync]
    HRESULT GetWindow
    (
        [out] HWND *phwnd
    );

К сожалению, документы для IOleWindow не упоминают об этоматрибут, но документы для некоторых других, таких как IOleDocumentView :: SetRect () do:

Этот метод определен с атрибутом [input_sync], что означает, что объект представления не может датьили выполнить другой, не входящий в RPC вызов RPC при выполнении этого метода.

Идея этого атрибута заключается в том, чтобы гарантировать вызывающей стороне (которая может быть приложением, таким как Word или другой управляющий хост OLE), что он может безопасно вызывать эти методы, не беспокоясь о повторном входе.

Ситуация усложняется тем, что COM решает применить это: он отклоняет межквартирные вызовы метода [input_sync], если считает, что может нарушить эти ограничения.Таким образом, IIRC, вы не можете сделать вызов [input_sync] между квартирами, если находитесь в SendMessage () - это тот случай, когда сообщение об ошибке несколько намекает.И - это то, что приводит вас сюда - вы не можете вызывать перекрестный метод [input_sync] из потока MTA.Возможно, COM немного усердствует в своем применении здесь, но с этим вам все равно придется иметь дело.

(Краткий комментарий о потоках MTA против STA: в COM потоки и объекты являются либо STA, либо MTASTA, Single-Threaded-Aparment, - это способ работы пользовательского интерфейса Windows: один поток владеет пользовательским интерфейсом и всеми объектами, связанными с ним, и эти объекты ожидают вызова только этим потоком. MTA или Multi-Threaded-Aparment,является более свободным для всех, объекты могут ожидать вызова из любого потока в любое время, поэтому необходимо выполнить их собственную синхронизацию, чтобы быть потокобезопасными. Потоки MTA обычно используются для рабочих и фоновых задач.управлять пользовательским интерфейсом в одном потоке STA, но загружать кучу файлов в фоновом режиме при использовании одного или нескольких потоков MTA. COM выполняет кучу работы, чтобы позволить двум взаимодействовать друг с другом, и пытается скрыть некоторые сложности.проблема заключается в том, что вы смешиваете эти метафоры: ThreadPools связаны с фоновой работой, как и MTA, ноIOleWindow ориентирован на пользовательский интерфейс, так же как и STA - и GetWindow оказывается единственным методом, который действительно очень строго соблюдает это.)

Короче говоря, вы не можете вызвать этот метод из потока ThreadPool, потому чтоони MTA темы.Кроме того, новые потоки по умолчанию являются MTA, поэтому недостаточно просто создать новый поток для выполнения работы.

Вместо этого создайте новый поток, но перед его запуском используйте tempThread.SetApartmentState(ApartmentState.STA);, это даст вамСТА нить.Возможно, вам придется поместить в этот поток STA всех кода, который имеет дело с COM-объектом оболочки, а не только один вызов GetWindow () - точные детали я не помню, но еслив конечном итоге вы получаете исходный COM-объект (здесь, похоже, ShellWindows), находясь в потоке MTA ThreadPool, он останется связанным с этим MTA, даже если вы попытаетесь вызвать его из STA.

Если вывместо этого можно было бы выполнять всю работу из потока STA, а не из MTA из ThreadPool, тем лучше, что в первую очередь этого можно было бы избежать.Вместо того, чтобы использовать System.Threading.Timer, который предназначен для фонового / не-пользовательского кода кода, попробуйте вместо этого использовать UI-центрированный System.Windows.Forms.Timer .Это действительно требует цикла сообщений - если у вас уже есть окна и формы в вашем приложении, у вас уже есть один, но если нет, самый простой способ сделать это в тестовом коде - это сделать MessageBox () в том же самомместо, где ваш код основной линии ожидает выхода (обычно с помощью Sleep или Console.ReadLine или аналогичного).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...