今天上午在运行系统时,意外发现在演示模式下停止监护时,视图中报警提示仍然存在。奇怪,以前好像不会有这种现象发生啊,中断运行时才发现系统发生了一个异常,查看log,原来异常为:“线程间操作无效: 从不是创建控件‘btStartOrStopMonitor’的线程访问它。”。问题出现在哪里呢,接着重新运行系统,单步跟踪到如下一句代码时跳转到异常:
m_MenuItem_StartOrStopMonitor.btStartOrStopMonitor.Text = GetString("停止监护");
这时觉得更奇怪了,简单的一句文本属性赋值都会出错?不过根据异常描述,应该是多线程问题,情况似乎没有那么简单。可能是系统规定创建按钮资源的线程与访问它的线程必须是同一个线程。后来查MSDN时,证实了这种判断,而且是VS.NET2005新增的约束。再找到一些资料后我将上述代码作了以下更改:
m_MenuItem_StartOrStopMonitor.SetButtonText(GetString("停止监护"));
其中函数SetButtonText是我在类m_MenuItem_StartOrStopMonitor中自定义的,目的是为了作线程判断处理,其实现为:
public delegate void SetTextCallback(string txt);
public void SetButtonText(string txt)
{
Debug.Assert(txt != null && txt != string.Empty);
if (this.btStartOrStopMonitor.InvokeRequired)
{
SetTextCallback stc = new SetTextCallback(SetButtonText);
this.Invoke(stc, txt);
}
else
{
this.btStartOrStopMonitor.Text = txt;
}
}
其中属性InvokeRequired是确定调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方可能是位于创建控件所在的线程以外的线程中。如果是另外的线程,则转到当前线程(创建视图m_MenuItem_StartOrStopMonitor的线程)执行,而且执行相当的函数SetButtonText,第二次进来后就会执行else代码块,这样就完成将另外的线程调用方法封送到当前线程处理)。保存后运行,果然有有效,停止监护后报警也随之取消了。
接着操作了几次起/停监护后,关掉演示模式时,系统竟然动不了,中断运行时在如下代码段停住了:
lock(bunk.SyncRoot)
{ .....
死锁了?应该是刚才的线程处理造成的,我想。因为上述线程切换后是同步执行的,这时其他的线程必须要等待它处理完。那如果改为异步处理应该会解决这个冲突的,于是接着修改上述代码:
public delegate void SetTextCallback(string txt);
IAsyncResult ar = null;
public void SetButtonText(string txt)
{
Debug.Assert(txt != null && txt != string.Empty);
if (this.btStartOrStopMonitor.InvokeRequired)
{
SetTextCallback stc = new SetTextCallback(SetButtonText);
ar = this.BeginInvoke(stc, txt);
}
else
{
this.btStartOrStopMonitor.Text = txt;
this.EndInvoke(ar);
}
}
此时在线程切换后,可以进入异步调用,使其他的线程可获得CPU时间而执行。再将系统运行起来后,这个问题果然迎刃而解。问题虽然解决了,但其个中奥秘还待进一步的了解。