同一线程在离开前重新进入方法

Same thread re-enters method before leaving

我们正在尝试从硬件设备读取信息,唯一的方法是通过硬件制造商提供的闭源本机 DLL 与其通信。他们还提供了一个 .NET 包装器来访问 DLL,下面简化了关注的包装器方法:

[DllImport("hardware_mfg.dll")]
private static extern int hardware_command_unicode(MarshalAs(UnmanagedType.LPWStr)] string outdata, uint outcount, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder indata, uint maxdata, ref uint incount);

public static int HardwareCommand(string senddata, StringBuilder recdata)
{
    uint incount = 0;
    return HardwareWrapper.hardware_command_unicode(senddata, (uint)senddata.Length, recdata, (uint)recdata.Capacity, ref incount);
}

调用HardwareWrapper.HardwareCommand函数的代码为:

// This method gets called from a DispatcherTimer.Tick event
public static int SendCommand(string command, StringBuilder buffer)
{
    lock (_locker)
    {
        try
        {
            if (_commandInProgress)
            {
                // This exception gets thrown
                throw new InvalidOperationException("This should not be possible");
            }
            _commandInProgress = true;
            // InvalidOperationException gets thrown while previous call to HardwareCommand here has not yet returned
            var result = HardwareWrapper.HardwareCommand(command, buffer);
            return result;
        }
        finally
        {
            _commandInProgress = false;
        }
    }        
}

令人困惑的部分是抛出了 InvalidOperationException。当主线程进入var result = HardwareWrapper.HardwareCommand(...)时有可能再次调用该方法,并进入与第一次调用returns之前相同的函数。抛出异常是间歇性的,但是让此代码 运行 持续 15-30 秒就足以使异常发生。

  1. 主线程怎么可能在一个方法中存在两次?
  2. 可以做些什么来防止这种情况发生?

编辑 1:将锁移动到外部范围

没有好的Minimal, Complete, and Verifiable code example it's impossible to provide a specific and complete diagnosis. But based on the information here, undoubtedly this is exactly :"your unmanaged code is pumping the queue".

COM 尤其以这种方式臭名昭著,但它也可能以其他方式出现。通常,本机代码会进入某种等待状态,在该状态下,线程的部分或所有消息仍会被分派。这可以包括计时器的 WM_TIMER 消息,导致 Tick 事件再次引发,甚至在前一个事件处理程序返回之前。

因为它在同一个线程中,所以 lock 是无关紧要的。 lock 使用的 Monitor 只阻塞线程 other 而不是持有锁的线程;当前线程可以根据需要重新进入该监视器保护的任何代码段。

您的 InvalidOperationException"This should not be possible" 中的消息不正确。有可能,应该有可能。无论好坏,这就是 Windows 中消息的工作方式。

根据您的目标和所涉及代码的具体情况(您尚未提供),您至少有几个选择:

  1. 不要使用 DispatcherTimer。相反,使用另一个计时器 类,它使用线程池来引发计时器事件。这些不依赖于消息队列,因此抽取消息不会影响计时器事件的引发方式。当然,这是假设您不需要执行 UI 线程中的代码。从问题中不清楚您的情况是否属于这种情况。 (实际上,即使您确实需要在按住 lock 的同时在 UI 线程中执行某些代码,也有可能使这种方法起作用,但它会变得棘手……最好避免这样做,如果你可以帮忙。)

  2. 使用_commandInProgress变量检测情况,如果标志已经设置为true则忽略计时器事件。当然,这假设您不需要在每个计时器事件上都执行命令,并且有一些合理的方法可以跳过这样做(包括处理调用本机代码时缺少结果值)。同样,问题中没有足够的信息来判断是否属于这种情况。