同一线程在离开前重新进入方法
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:将锁移动到外部范围
没有好的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 中消息的工作方式。
根据您的目标和所涉及代码的具体情况(您尚未提供),您至少有几个选择:
不要使用 DispatcherTimer
。相反,使用另一个计时器 类,它使用线程池来引发计时器事件。这些不依赖于消息队列,因此抽取消息不会影响计时器事件的引发方式。当然,这是假设您不需要执行 UI 线程中的代码。从问题中不清楚您的情况是否属于这种情况。 (实际上,即使您确实需要在按住 lock
的同时在 UI 线程中执行某些代码,也有可能使这种方法起作用,但它会变得棘手……最好避免这样做,如果你可以帮忙。)
使用_commandInProgress
变量检测情况,如果标志已经设置为true
则忽略计时器事件。当然,这假设您不需要在每个计时器事件上都执行命令,并且有一些合理的方法可以跳过这样做(包括处理调用本机代码时缺少结果值)。同样,问题中没有足够的信息来判断是否属于这种情况。
我们正在尝试从硬件设备读取信息,唯一的方法是通过硬件制造商提供的闭源本机 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:将锁移动到外部范围
没有好的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
COM 尤其以这种方式臭名昭著,但它也可能以其他方式出现。通常,本机代码会进入某种等待状态,在该状态下,线程的部分或所有消息仍会被分派。这可以包括计时器的 WM_TIMER 消息,导致 Tick
事件再次引发,甚至在前一个事件处理程序返回之前。
因为它在同一个线程中,所以 lock
是无关紧要的。 lock
使用的 Monitor
只阻塞线程 other 而不是持有锁的线程;当前线程可以根据需要重新进入该监视器保护的任何代码段。
您的 InvalidOperationException
、"This should not be possible" 中的消息不正确。有可能,应该有可能。无论好坏,这就是 Windows 中消息的工作方式。
根据您的目标和所涉及代码的具体情况(您尚未提供),您至少有几个选择:
不要使用
DispatcherTimer
。相反,使用另一个计时器 类,它使用线程池来引发计时器事件。这些不依赖于消息队列,因此抽取消息不会影响计时器事件的引发方式。当然,这是假设您不需要执行 UI 线程中的代码。从问题中不清楚您的情况是否属于这种情况。 (实际上,即使您确实需要在按住lock
的同时在 UI 线程中执行某些代码,也有可能使这种方法起作用,但它会变得棘手……最好避免这样做,如果你可以帮忙。)使用
_commandInProgress
变量检测情况,如果标志已经设置为true
则忽略计时器事件。当然,这假设您不需要在每个计时器事件上都执行命令,并且有一些合理的方法可以跳过这样做(包括处理调用本机代码时缺少结果值)。同样,问题中没有足够的信息来判断是否属于这种情况。