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

本文关键字:调用 应用程序 调度 输入 同步 | 更新日期: 2023-09-27 18:20:04

我从System.Thread.Timer线程池中得到了这个(上面标题中的错误),所以我有了TimerWrapper,它包装了System.Thread.Time,将实际执行移动到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);
    }

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

祝贺您;你偶然发现了我最喜欢的COM怪癖之一,在这种情况下,IOleWindow的GetWindow方法有一个令人愉快的模糊限制,以及一条错误消息,它几乎没有告诉你发生了什么。这里的根本问题是GetWindow()方法被标记为[input_sync],来自SDK中的include''oleidl.idl文件:

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

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

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

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

事情变得棘手的是,COM决定强制执行这一点:如果它认为可能违反这些约束,它将拒绝对[input_sync]方法的跨单元调用。所以,IIRC,如果你在SendMessage()中,你就不能进行跨单元[input_sync]调用-错误消息在某种程度上暗示了这一点。而且,这就是让你走到这一步的原因-你不能从MTA线程调用跨单元[imput_sync'方法。也许COM在执行方面有点过于热心,但这是你无论如何都要处理的问题。

(关于MTA与STA线程的简要评论:在COM中,线程和对象要么是STA,要么是MTA。STA,单线程单元,是Windows UI的工作方式;一个线程拥有UI和与其关联的所有对象,这些对象希望由该线程单独调用。MTA,或多线程单元,更像是一个免费的;对象可以随时从任何线程调用,所以eed进行自己的同步以确保线程安全。MTA线程通常用于辅助任务和后台任务。因此,您可以在单个STA线程上管理UI,但可以在后台使用一个或多个MTA线程下载一堆文件。COM做了大量工作来允许两者相互操作,并试图隐藏一些复杂性。这里的部分问题是你混淆了这些比喻:线程池与后台工作相关,MTA也是,但IOleWindow是以UI为中心的,STA也是——而GetWindow恰好是一个真正严格执行这一点的方法。)

长话短说,您不能从ThreadPool调用此方法,因为它们是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或类似工具)。