我从System.Thread.Timer线程池中得到了这个(上面标题中的错误),所以我有了包装了System.Thread.Timer的TimerWrapper,将实际执行的代码移到了System.Thread.ThreadPool,我仍然得到了它我将其移动到新的Thread(callback).Start(),但仍然得到它。当我把它放在一个全新的线程上时,它如何调度一个输入同步调用?

这是一个非常小的原型(prototype)应用程序,其中我正在做的是触发执行此操作的计时器...

    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);
    }

最佳答案

恭喜!您已经偶然发现了我最喜欢的COM怪癖之一,在这种情况下,IOleWindow的GetWindow方法令人难以理解的限制-以及一条错误消息,使您对正在发生的事情一无所知。潜在的问题是,从SDK中的include \ oleidl.idl文件将GetWindow()方法标记为[input_sync]:

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

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



该属性背后的想法是向调用方(可以是诸如Word或其他OLE控件主机之类的应用程序)保证调用者可以安全地调用这些方法而不必担心重入。

棘手的地方是COM决定强制执行此操作:如果认为可能违反这些约束,它将拒绝对[input_sync]方法的跨部门调用。因此,IIRC,如果您在SendMessage()中,则无法进行跨部门的[input_sync]调用-在这种情况下,错误消息有些暗示。而且-这就是让您到达这里的方法-您不能从MTA线程调用跨部门的[input_sync]方法。也许COM在这里的执行有点过分热心,但这是您必须面对的问题。

(关于MTA与STA线程的简短评论:在COM中,线程和对象是STA或MTA。STA是Windows UI工作方式的单线程配置;单线程拥有UI和与之关联的所有对象,以及MTA或Multi-Threaded-Aparment基本上是免费的;对象可以在任何时间从任何线程调用,因此需要进行自己的同步为了确保线程安全,MTA线程通常用于辅助任务和后台任务,因此您可以在单个STA线程上管理UI,但是在使用一个或多个MTA线程的情况下在后台下载一堆文件。努力使两者互操作并尝试隐藏一些复杂性这里的部分问题是您正在混合使用这些隐喻:ThreadPools与后台工作相关联,MTA与后台工作相关联,但是IOleWindow以UI为中心,因此是STA-而GetWindow恰好是对enfor严格要求严格的一种方法强制执行此操作。)

长话短说,您不能从ThreadPool thead调用此方法,因为它们是MTA线程。另外,默认情况下,新线程是MTA,因此仅创建一个新线程来进行工作就不够了。

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

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

关于c# - 由于应用程序正在调度输入同步调用,因此无法进行传出调用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8839195/

10-12 07:05