我从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);
}
答案 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或类似内容)。