关于在 C# (WPF) 中对 COM 对象进行异步调用的问题

Posted

技术标签:

【中文标题】关于在 C# (WPF) 中对 COM 对象进行异步调用的问题【英文标题】:Question about making Asynchronous call in C# (WPF) to COM object 【发布时间】:2011-02-27 11:16:30 【问题描述】:

很抱歉问了这么一个基本问题,但我似乎对这个问题感到头晕目眩!我正在从我的 WPF 项目中调用一个 COM (ATL) 对象。 COM 方法可能需要很长时间才能完成。我想我会尝试异步调用它。我有几个演示行显示了问题。

private void checkBox1_Checked(object sender, RoutedEventArgs e)
 
      //DoSomeWork();
      AsyncDoWork caller = new AsyncDoWork(DoSomeWork);
      IAsyncResult result = caller.BeginInvoke(null, null);            
  

private delegate void AsyncDoWork();
private void DoSomeWork()

   _Server.DoWork();

ATL 方法 DoWork 非常令人兴奋。它是:

STDMETHODIMP CSimpleObject::DoWork(void)

   Sleep(5000);
   return S_OK;

我曾期望以这种方式运行会导致复选框被立即选中(而不是在 5 秒内),并且我能够在屏幕上移动 WPF gui。我不能 - 5 秒钟。

我做错了什么?我敢肯定这很简单。委托人签名错误?

谢谢。

【问题讨论】:

我对此做了更多的思考和测试。我认为 C# 代码没有任何问题。当我用 private void DoSomeWork() Thread.Sleep(5000); 替换 ATL 调用时一切正常。我认为发生的事情是 ATL 是一个 STA 对象。因此,即使调用最初是在工作线程上进行的,ATL 端的实际工作也是在主 GUI 线程上完成的。解决它的一种(痛苦的)方法是使 COM 组件成为 MTA。 【参考方案1】:

我确信您对 ATL 代码的调用被编组到 GUI 线程是正确的,因为 ATL 代码是 STA,从而阻塞了您的 GUI 线程。

两种解决方案:

    将 ATL 部分重新架构为 MTA,这可能不可行,或者 将 ATL 保留为 STA,但最初在为此目的创建的线程中构造 COM 对象,因此它将获得不同的单元。

一个 WPF 应用程序实际上可以使用多个 UI 线程正常运行,只要每个 UI 线程都管理自己的 UI 部分,并且这些部分由 HwndSource 分隔。换句话说,运行部分 UI 的第二个线程实现了一个 Win32 HWND,然后将其嵌入到由主线程运行的部分 UI 中。

如果您的 COM 对象本身不是 GUI 对象,那么在单独的工作线程中构造它并将其保留在那里应该很容易。由于它是一个 STA 对象,所有调用都将被封送到另一个线程。

【讨论】:

感谢您的回答。我认为将 ATL 部分更改为 MTA 实际上相当容易。只是出于好奇,您是否提到了具有多个 UI 线程的 WPF 应用程序? 我已经在我自己的一些应用程序中做到了,这并不难。以下是一些涵盖它各个方面的链接:blogs.msdn.com/b/changov/archive/2009/10/26/…msdn.microsoft.com/en-us/library/bb909794(VS.90).aspxeprystupa.wordpress.com/2008/07/28/…drdobbs.com/windows/197003872【参考方案2】:

BeginInvoke 仍将在同一个线程上执行您的调用,只是异步执行*。您可以创建一个新的Thread 对象:

Thread comthread = new Thread(new ThreadStart(delegate()  DoSomeWork(); ));
comthread.Start();

或试用 .Net 4 的新 Task library:

Task.Factory.StartNew(() =>

    DoSomeWork();
);

它们本质上是一样的。**

*委托类型的BeginInvoke 方法与调用者在同一线程上执行,但在后台执行。我不确定是否有关于何时执行的规则,但它肯定不是你想要的顺序。但是,像 BeginRead 这样的异步方法在与主线程分开的特殊线程上执行。 **略有不同 - Thread 方法总是会创建一个新的Thread 对象,而Task 系统有一个线程池可供使用,理论上效率更高。

【讨论】:

JustABill,感谢您的快速回复。启动一个新线程也没有解决问题。见我上面的评论;我认为问题在于 ATL 对象存在于单线程单元中。顺便说一句,一旦我将代码替换为仅休眠 5 秒而不进行 COM 调用,BeginInvoke 工作正常(就像您的新 Thread(...) 想法一样)并且清楚地表明工作(在这种情况下是休眠)是在工作线程上完成。所以我没有看到“BeginInvoke 方法在同一个线程上执行......”。我没有 .Net 4.0 的选项 - Task.Factory 听起来很整洁。谢谢。【参考方案3】:

我对此做了更多的思考和测试。 C# 代码没有任何问题。如果 ATL 对象是 STA 对象(在我的情况下),它将在主线程上调用,而不管 C# 代码是否尝试在工作线程上调用它。将 ATL 对象更改为 MTA 对象可以异步调用它。

【讨论】:

以上是关于关于在 C# (WPF) 中对 COM 对象进行异步调用的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何根据用户输入在 C# 中对对象数组进行排序?

C# WPF datagrid checkboxcolumn 使用问题

如何在C#中对列表进行排序[重复]

在 WPF 中对 DataGrid 进行预排序

如何在 C# 中对字符串进行 URL 编码

C#关于定时器和多线程中对控件的操作以及界面假死的现象。