同一线程在离开之前重新进入方法
本文关键字:新进入 方法 离开 一线 线程 | 更新日期: 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秒就足够让异常发生了。
- 主线程怎么可能在一个方法中存在两次?
- 可以做些什么来防止这种情况发生?
EDIT 1:将锁移到外部作用域
如果没有一个好的最小化、完整和可验证的代码示例,就不可能提供一个具体和完整的诊断。但是根据这里的信息,毫无疑问,这正是@David所说的:"您的非托管代码正在抽取队列"。
COM尤其臭名昭著,但它也可能以其他方式发生。通常,本机代码会进入某种等待状态,在这种状态下,线程的部分或全部消息仍会被分派。这可以包括定时器的WM_TIMER消息,导致Tick
事件再次被引发,甚至在前一个事件处理程序返回之前。
因为它在同一个线程中,lock
是不相关的。lock
使用的Monitor
只阻塞除持有锁的线程之外的其他线程;当前线程可以随心所欲地重新进入受该监视器保护的任何代码段。
您的InvalidOperationException
中的消息"This should not be possible"是不正确的。这是可能的,而且应该是可能的。不管怎样,这就是Windows中的消息工作方式。
根据您的目标和所涉及的代码的细节(您没有提供),您至少有两个选项:
-
不要使用
DispatcherTimer
。相反,使用其他计时器类之一,它们使用线程池引发计时器事件。它们不依赖于消息队列,因此抽取消息不会影响计时器事件的引发方式。当然,这假定您不需要在UI线程中执行代码。你的情况是否如此,从问题中看不出来。(实际上,即使你确实需要在UI线程中执行一些代码而持有lock
,也有可能让这种方法工作,但如果你能帮助它,最好避免这样做。) -
使用
_commandInProgress
变量检测情况,如果标志已经设置为true
,则忽略定时器事件。当然,这假设您不需要对每个计时器事件执行命令,并且有一些合理的方法可以跳过这样做(包括处理调用本机代码时缺少结果值的情况)。同样,问题中没有足够的信息来确定是否存在这种情况。