我有一个 C# 应用程序来处理 PLC(控制硬件设备的西门子 cpu)事件。所有事件都在单独的线程中处理,所有这些线程都是一个大的 try/catch/finally 块来锁定数据库,然后最终释放锁。它运行良好,直到我放置 Thread.Sleep() 以避免处理重复的 PLC 事件(因为有时 PLC 触发事件但数据仍未更新,所以如果我发现数据与以前的数据相同,我想休眠 1 秒)。
我放置了一些日志来调试这个问题,我发现处理线程在没有运行finally块的情况下消失了。线程main()是这样的:
virtual protected void Thread1Main()
{
DB.DeviceLock(config.position);
if (config.position == 4)
Logger.I("DeviceLock 4");
try
{
if (_exchange.plc_long > 0 && _exchange.plc_short > 0 && _exchange.plc_long == _last_long && _exchange.plc_short == _last_short)
{
_last_long = 0;
_last_short = 0;
if (config.position == 4)
Logger.I("Before sleep 4");
Thread.Sleep(1000);
if (config.position == 4)
Logger.I("After sleep 4");
return;
}
_last_long = _exchange.plc_long;
_last_short = _exchange.plc_short;
.......
if (config.position == 4)
Logger.I("end 4");
}
catch (Exception ex)
{
if (config.position == 4)
Logger.I("exception 4");
Logger.E(this, ex.Message);
}
finally
{
if (config.position == 4)
Logger.I("finally 4");
DB.DeviceUnlock(config.position);
_thread = null;
}
}
这里我有 DB.DeviceLock() 仅锁定此设备,并且我有 DLock() 锁定所有设备。 DB代码是这样的:
static public void DBLock()
{
Logger.I("DBLock:Before lock");
lock (_locker)
{
while (_deviceLocks.MyCount() > 0)
{
foreach (var d in _deviceLocks.MyToArray())
Logger.W($"DBLock:Wait device {d} to unlock");
Thread.Sleep(1000);
};
_dbLock = 1;
Logger.I("DBLock:Locked");
}
}
static public void DBUnlock()
{
Logger.I("DBLock:Unlocked");
_dbLock = 0;
}
static public void DeviceLock(int d)
{
lock(_locker)
{
while (_dbLock > 0)
{
Logger.W($"DBLock:Device {d} waiting DBLock");
Thread.Sleep(1000);
};
_deviceLocks.MyAdd(d);
}
}
static public void DeviceUnlock(int d)
{
_deviceLocks.MyRemove(d);
if (_deviceLocks.MyCount() > 0)
Logger.W($"DBLock:released {d}, still locked: " + string.Join(",", _deviceLocks.MyToArray()));
}
在日志中,当我故意放入可疑数据以使其睡眠 - >处理 - >睡眠 - >处理时,我确实看到了正确的日志,即使执行了finally块:
I 16:58:47.798 :DeviceLock 4
I 16:58:47.798 :Before sleep 4
I 16:58:48.799 :After sleep 4
I 16:58:48.799 :finally 4
I 16:58:48.834 :DeviceLock 4
W 16:58:48.842 :DB locks:released 701, still locked: 4
I 16:58:48.848 :end 4
I 16:58:48.848 :finally 4
I 16:58:48.935 :DeviceLock 4
I 16:58:48.935 :Before sleep 4
问题是当我调用 DLock() 时,一切都会停止。 DLock一直等待设备4解锁,但是设备4线程消失了!它再也不会回来,它的finally块没有被执行,并且在调试模式下,我在Visual Studio的线程视图中找不到该线程。日志变成这样:
I 16:58:52.355 :DeviceLock 4 <-- this is the last log of device 4
I 16:58:53.766 :DBLock:Before lock
W 16:58:53.766 :DBLock:Wait device 4 to unlock
W 16:59:02.774 :DBLock:Wait device 4 to unlock
然后无穷无尽
W 16:59:02.774 :DBLock:Wait device 4 to unlock
所以我的问题是:为什么线程在没有运行finally块的情况下消失了?
添加更多信息:
我发现这段代码可能会导致问题:
public void RefreshAlertIcon(Device dev,int error)
{
if (Dispatcher.CheckAccess())
{
if (_d2icon.ContainsKey(dev.config))
_d2icon[dev.config].ShowAlert(error > 0 && !_d2icon[dev.config]._alert);
}
else
Dispatcher.Invoke(delegate {
if (_d2icon.ContainsKey(dev.config))
_d2icon[dev.config].ShowAlert(error > 0 && !_d2icon[dev.config]._alert);
});
}
RefreshAlertIcon 在 Thread1Main() 中被调用,..... 部分。那么 Dispatcher 导致线程消失,我是否正确使用 Dispatcher?
最后我发现将Dispatcher.Invoke更改为InvokeAsync解决了问题。看来 Dispatcher.Invoke 有自己的锁,可能无法从 UI 线程本身调用。所以最后的代码如下所示:
Dispatcher.InvokeAsync(delegate {
if (_d2icon.ContainsKey(dev.config))
_d2icon[dev.config].ShowAlert(error > 0 && !_d2icon[dev.config]._alert);
});
我们的应用程序现在已经连续工作1周没有问题!太棒了!