在离开之前,相同的线程重新进入方法

时间:2016-08-22 13:13:01

标签: c# .net wpf multithreading dllimport

我们正在尝试从硬件设备读取信息,唯一的方法是通过硬件制造商提供的封闭源本机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. 可以采取哪些措施来防止这种情况发生?
  3. 编辑1:将锁定移至外部范围

1 个答案:

答案 0 :(得分:1)

如果没有良好的Minimal, Complete, and Verifiable code example,就无法提供具体而完整的诊断。但根据这里的信息,毫无疑问,这正是as @David says"您的非托管代码正在抽取队列"

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

由于它位于同一个帖子中,lock无关紧要。 Monitor使用的lock仅阻止线程其他而不是持有锁的线程;当前线程可以根据需要重新输入受该监视器保护的任何代码段。

InvalidOperationException&#34中的消息;这不可能" ,不正确。它是可能的,应该是可能的。无论好坏,它都是Windows中消息的工作方式。

根据您的目标和相关代码的具体情况(您尚未提供),您至少有以下几种选择:

  1. 不要使用DispatcherTimer。而是使用其他一个计时器类,它使用线程池来引发计时器事件。这些不依赖于消息队列,因此泵送消息不会影响定时器事件的引发方式。当然,这假设您不需要在UI线程中执行代码。在这种情况下是否属于这种情况,问题并不明确。 (实际上,即使你需要在持有lock的同时在UI线程中执行一些代码,也可以使这种方法起作用,但它变得棘手......如果你能帮助它,最好避免这样做。 )

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