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

本文关键字:新进入 方法 离开 一线 线程 | 更新日期: 2023-09-27 18:03:22

我们正在尝试从硬件设备读取信息,而做到这一点的唯一方法是通过硬件制造商提供的闭源本地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(...)时,有可能再次调用该方法,并在第一次调用返回之前进入相同的函数。异常的抛出是间歇性的,但是让这段代码运行15-30秒就足够让异常发生了。

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

EDIT 1:将锁移到外部作用域

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

如果没有一个好的最小化、完整和可验证的代码示例,就不可能提供一个具体和完整的诊断。但是根据这里的信息,毫无疑问,这正是@David所说的:"您的非托管代码正在抽取队列"

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

因为它在同一个线程中,lock是不相关的。lock使用的Monitor只阻塞除持有锁的线程之外的其他线程;当前线程可以随心所欲地重新进入受该监视器保护的任何代码段。

您的InvalidOperationException中的消息"This should not be possible"是不正确的。这是可能的,而且应该是可能的。不管怎样,这就是Windows中的消息工作方式。

根据您的目标和所涉及的代码的细节(您没有提供),您至少有两个选项:

  1. 不要使用DispatcherTimer。相反,使用其他计时器类之一,它们使用线程池引发计时器事件。它们不依赖于消息队列,因此抽取消息不会影响计时器事件的引发方式。当然,这假定您不需要在UI线程中执行代码。你的情况是否如此,从问题中看不出来。(实际上,即使你确实需要在UI线程中执行一些代码而持有lock,也有可能让这种方法工作,但如果你能帮助它,最好避免这样做。)

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