我怎么能相信'锁'了?

cab*_*bbi -3 c# multithreading thread-safety

回到我原来的非托管c ++时代,我可以信任多线程应用程序中的关键部分.所以,现在使用dotNet/C#,我正在转发锁定机制.通过锁定资源,我确信任何线程都无法在我的代码段中访问这些资源.

这在dotNet中似乎不是真的!

我有我的Windows服务应用程序.我创建了一个主托管线程,其中隐藏的表单托管了第三个奇偶校验OCX.在这个线程中,我做消息在一个对象列表上进行轮询.此管理线程中的OCX触发的事件会修改此对象列表.

我在这里发布了我的代码的简化部分:

  public bool Start()
  {
     ServiceIsRunning = true;

     m_TaskThread = new Thread(new ParameterizedThreadStart(TaskLoop));
     m_TaskThread.SetApartmentState(ApartmentState.STA);
     m_TaskThread.Start(this);

     return true;
  }

  private void OnOCXEvent(object objToAdd)
  {
     lock(m_ObjectList)
     {
        m_ObjectList.Add(objToAdd);            }
     }
  }

  private void CheckList()
  {
     lock(m_ObjectList)
     {
        foreach(object obj in m_ObjectList)
        {
           ...
        }
     }
  }

  [STAThread] // OCX requirement!
  private void TaskLoop(object startParam)
  {
     try {

        ... 

        while (ServiceIsRunning)
        {
           // Message pump 
           Application.DoEvents();

           if (checkTimeout.IsElapsed(true))
           {
              CheckList();
           }

           // Relax process CPU time!
           Thread.Sleep(10);
        }
     } catch(Exception ex) {
        ... 
     }
  }
Run Code Online (Sandbox Code Playgroud)

你不会相信我:我在CheckList中得到了一个'list has modified >>例外!8 /

所以我做了一些日志记录,我注意到当SAME托管线程在CheckList foreach循环中时,将引发OnOCXEvent .我敢肯定:我的日志文件中有相同的托管线程ID,foreach循环未完成,并且OnOCXEvent已被同一个manged线程调用!

现在我想知道:这怎么可能发生?是否使用更多win32线程实现了单个托管线程?

希望有人能解释为什么会这样,所以我可以解决这个问题.

谢谢,法比奥

我的注意:

我实际上解决了在foreach循环之前创建列表副本的问题.但我不喜欢这个解决方案.我也想了解发生了什么.我没有第三个奇偶校验OCX代码,但我在CheckList循环中调用的方法在逻辑上与被触发的OCX事件无关.

Jon*_*eet 6

我强烈怀疑这只是一个重新入侵的问题.

在您的CheckList通话中,您正在调用OCX方法.如果它能做任何本身可以引发OCX事件的事情 - 包括有效调用Application.DoEvents- 那么你最终OnOCXEvent可能会在一个也在执行的线程中被调用CheckList......这将导致问题.

这不是一个问题lock- 这是一个重新入侵的问题.

诊断的一种方法是修改CheckListOnOCXEvent方法:

private bool inCheckList;

private void OnOCXEvent(object objToAdd)
{
   lock(m_ObjectList)
   {
      if (inCheckList)
      {
         throw new Exception("Look at this stack trace!");
      }
      m_ObjectList.Add(objToAdd);
   }
}

private void CheckList()
{
   lock(m_ObjectList)
   {
      inCheckList = true;
      foreach(object obj in m_ObjectList)
      {
         ...
      }
      inCheckList = false; // Put this in a finally block if you really want
   }
}
Run Code Online (Sandbox Code Playgroud)

我强烈怀疑你会看到一个堆栈跟踪,其中包括抛出的异常CheckList,OnOCXEvent-和一堆在中间代码,与运行在中间的消息循环的东西.

  • @Cabbi:不,这就是为什么你*不应该感到惊讶 - 并且不应该把责任归咎于'锁定'.你已经在一个线程中重新进入,所以`lock`对于这项工作来说只是一个不合适的工具.一个不可重入的锁也无法帮助你 - 你只是让一个线程自身陷入僵局. (3认同)