由于应用程序正在调度输入同步调用,因此无法进行传出呼叫

时间:2012-01-12 17:10:58

标签: c# internet-explorer com

我从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 个答案:

答案 0 :(得分:33)

恭喜;你已经设法偶然发现了我最喜欢的COM怪癖之一,在这种情况下,使用IOleWindow的GetWindow方法有一个令人愉快的模糊限制 - 以及一条错误消息,它给你一些关于发生了什么的线索。这里的根本问题是GetWindow()方法被标记为[input_sync] - 来自SDK中的include \ oleidl.idl文件:

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

不幸的是,IOleWindow的文档没有提到这个属性,但其他一些文档(例如IOleDocumentView::SetRect())也没有提到:

此方法使用[input_sync]属性定义,这意味着在执行此方法时,视图对象无法生成或进行另一个非input_sync RPC调用。

这个属性背后的想法是保证调用者(可能是像Word或其他一些OLE控件主机这样的应用程序)可以安全地调用这些方法,而不必担心重入。

事情变得棘手的是,COM决定强制执行:如果它认为可能违反这些约束,它将拒绝对[input_sync]方法的跨公寓调用。所以,IIRC,如果你在SendMessage()内,你就不能进行跨公寓[input_sync]调用 - 这就是错误信息有点暗示的情况。并且 - 这是让你在这里的那个 - 你不能从MTA线程调用跨公寓[input_sync]方法。也许投诉人在执法方面有点过于热心,但无论如何你都要处理这件事。

(简要评论MTA与STA线程:在COM中,线程和对象是STA或MTA.STA,Single-Threaded-Aparment,是Windows UI的工作方式;单个线程拥有UI以及与之相关的所有对象它,并且那些对象期望被该线程单独调用.MTA或Multi-Threaded-Aparment更像是一个free-for-all;对象可以随时从任何线程调用,所以需要做他们自己的同步是线程安全的.MTA线程通常用于工作和后台任务。所以你可以在一个STA线程上管理UI,但是在后台使用一个或多个MTA线程下载一堆文件。一堆工作让两个人互相交互,并试图隐藏一些复杂性。这里的部分问题是你混合这些隐喻:ThreadPools与后台工作相关联,因此是MTA,但IOleWindow是UI-中心,STA也是如此 - 而GetWindow恰好是一种非常严格的方法强迫这个。)

长话短说,你不能从ThreadPool中调用这个方法,因为它们是MTA线程。此外,默认情况下新线程是MTA,所以只创建一个新线程来完成工作是不够的。

相反,创建新线程,但在启动之前使用tempThread.SetApartmentState(ApartmentState.STA);,这将为您提供一个STA线程。您可能需要实际放置处理该STA线程中的shell COM对象的所有代码,而不仅仅是对GetWindow()的单个调用 - 我不记得确切的详细信息,但是如果您在MTA ThreadPool线程上最终获取原始COM对象(此处似乎是ShellWindows),即使您尝试从STA调用它,它也将与该MTA保持关联。

如果你可以从一个STA线程而不是一个来自ThreadPool的MTA中完成所有的工作,那就更好了,这样就可以避免这种情况。而不是使用专为后台/非UI代码设计的System.Threading.Timer,而是尝试使用以UI为中心的System.Windows.Forms.Timer。这需要一个消息循环 - 如果你已经在你的应用程序中有窗口和表单,你已经有了一个,但如果没有,在测试代码中执行此操作的最简单方法是在同一个中执行一个MessageBox()主线代码等待退出的位置(通常使用Sleep或Console.ReadLine或类似内容)。