0x80010100:系统调用失败“异常,ContextSwitchDeadlock

时间:2012-02-21 11:28:45

标签: c# com apartments contextswitchdeadlock

长话短说:在一个与COM inproc-server(dll)一起工作的C#应用​​程序中,遇到“0x80010100:系统调用失败”异常,而在调试模式下也遇到ContextSwitchDeadlock异常。

现在更详细说明:

1)C#app初始化STA,创建一个COM对象(注册为“Apartment”);然后订阅它的连接点,并开始使用该对象。

2)在某个阶段,COM对象会生成许多事件,并将同一个公寓中创建的COM对象集合作为参数传递。

3)C#端的事件处理程序处理上面的集合,偶尔调用对象的一些方法。在某些阶段,后面的调用开始失败,但有上述例外。

在COM方面,公寓使用一个隐藏窗口,其winproc如下所示:

typedef std::function<void(void)> Functor;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)   
{   
  switch(msg)   
  {   
    case AM_FUNCTOR:
    {
      Functor *f = reinterpret_cast<Functor *>(lParam);
      (*f)();
      delete f;
    }
    break;   
    case WM_CLOSE:   
      DestroyWindow(hwnd);   
    break;   
    default:   
      return DefWindowProc(hwnd, msg, wParam, lParam);   
  }   
  return 0;   
} 

事件从COM服务器的其他部分发布到此窗口:

void post(const Functor &func)
{
  Functor *f = new Functor(func);
  PostMessage(hWind_, AM_FUNCTOR, 0, reinterpret_cast<LPARAM>(f));
}

这些事件是与实际参数绑定的标准ATL CP实现,它们归结为这样的事情:

pConnection->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);

在C#中,处理程序如下所示:

private void onEvent(IMyCollection objs)
{
  int len = objs.Count; // usually 10000 - 25000
  foreach (IMyObj obj in objs)
  {
    // some of the following calls fail with 0x80010100
    int id = obj.id;
    string name = obj.name;
    // etc...
  }
}

==================

那么,上述问题是否只是因为公寓的消息队列过载了它试图提供的事件?或者应该完全阻止消息循环导致这种行为?

让我们假设消息队列有2个连续事件,评估为“onEvent”调用。第一个输入C#托管代码,它尝试重新输入非托管代码,即同一个公寓。通常,这是允许的,我们做了很多。何时,在什么情况下会失败?

感谢。

1 个答案:

答案 0 :(得分:2)

即使有多个公寓,这也应该有效:

  • 只有一个线程响应外部事件,如网络流量,计时器,发布的消息等。
  • 其他线程仅为COM请求提供服务(即使它们在处理过程中回调主线程)。

  • 线程队列都没有变满,阻止COM与线程通信。

<强>首先: 看起来某些物体与其他物体不在同一个公寓中。您确定在STA中创建了所有对象吗?

你所描述的是一个典型的死锁 - 两个独立的线程,每个线程都在等待另一个。这就是我期望在不同线程上使用C#和COM端进行操作的设计。

如果所有对象在同一个线程上,以及该线程上的隐藏窗口,那么你应该没问题,所以我认为你需要检查一下。 (显然,这包括由COM端创建并传递给C#端的任何其他对象。)

您可以尝试通过在调试器中按“pause”并检查每个线程中的代码来调试它(如果您看到RPCRT * .DLL这意味着您正在查看代理)。或者,您可以从C#和COM端以及WndProc中的各个关键点DebugPrint当前线程ID - 它们应该都是相同的。

其次:它应该使用多个线程,前提是只有一个线程生成工作项,而另一个除了主机响应调用的COM对象之外什么都不做(即不生成调用从计时器,网络流量,发布消息等),在这种情况下,可能是线程队列已满,COM无法回复呼叫。

您应该使用受关键部分保护的双端队列,而不是使用线程队列。

  您可以在队列上保留一个项目计数器,以查看是否存在此问题。