好的,这就是我做的... 由于C#对我来说还有些陌生,评论将不胜感激!
让多个线程同时访问串口(或任何资源,特别是异步资源)是不可行的。为了在不完全重写应用程序的情况下修复它,我引入了一个锁SerialPortLockObject
来保证串口的独占访问,如下所示:
- GUI线程除了在有后台操作运行时,持有
SerialPortLockObject
。
SerialPort
类被包装,以便任何非持有SerialPortLockObject
的线程读取或写入时都会抛出异常(有助于发现几个争用错误)。
- 定时器类被包装(
SerialOperationTimer
类),使得后台工作函数被调用时都会通过获取SerialPortLockObject
进行括号封闭。
SerialOperationTimer
一次只允许运行一个计时器(有助于发现GUI在启动不同计时器之前忘记停止后台处理的多个bug)。这可以通过为计时器工作使用特定的线程,并使该线程在计时器处于活动状态时持有锁来改进(但这仍需要更多工作;按编码方式,System.Timers.Timer
从线程池运行工作函数)。
- 当停止
SerialOperationTimer
时,它会禁用基础计时器并刷新串口缓冲区(引发任何被阻塞的串口操作的异常,如上述可能的方法1所解释)。然后GUI线程重新获取 SerialPortLockObject
。
这是SerialPort
的包装器:
public class CheckedSerialPort : SafePort
{
private void checkOwnership()
{
try
{
if (Monitor.IsEntered(XXX_Conn.SerialPortLockObject)) return;
throw new Exception("Serial IO attempted without lock ownership");
}
catch (Exception ex)
{
StringBuilder sb = new StringBuilder("");
sb.AppendFormat("Message: {0}\n", ex.Message);
sb.AppendFormat("Exception Type: {0}\n", ex.GetType().FullName);
sb.AppendFormat("Source: {0}\n", ex.Source);
sb.AppendFormat("StackTrace: {0}\n", ex.StackTrace);
sb.AppendFormat("TargetSite: {0}", ex.TargetSite);
Console.Write(sb.ToString());
Debug.Assert(false);
throw;
}
}
public new int ReadByte() { checkOwnership(); return base.ReadByte(); }
public new string ReadTo(string value) { checkOwnership(); return base.ReadTo(value); }
public new string ReadExisting() { checkOwnership(); return base.ReadExisting(); }
public new void Write(string text) { checkOwnership(); base.Write(text); }
public new void WriteLine(string text) { checkOwnership(); base.WriteLine(text); }
public new void Write(byte[] buffer, int offset, int count) { checkOwnership(); base.Write(buffer, offset, count); }
public new void Write(char[] buffer, int offset, int count) { checkOwnership(); base.Write(buffer, offset, count); }
}
这里是 System.Timers.Timer
的包装器:
class SerialOperationTimer
{
private static SerialOperationTimer runningTimer = null;
private string name;
public delegate void SerialOperationTimerWorkerFunc_T(object source, System.Timers.ElapsedEventArgs e);
private SerialOperationTimerWorkerFunc_T workerFunc;
private System.Timers.Timer timer;
private object workerEnteredLock = new object();
private bool workerAlreadyEntered = false;
public SerialOperationTimer(string _name, int msecDelay, SerialOperationTimerWorkerFunc_T func)
{
name = _name;
workerFunc = func;
timer = new System.Timers.Timer(msecDelay);
timer.Elapsed += new System.Timers.ElapsedEventHandler(SerialOperationTimer_Tick);
}
private void SerialOperationTimer_Tick(object source, System.Timers.ElapsedEventArgs eventArgs)
{
lock (workerEnteredLock)
{
if (workerAlreadyEntered) return;
workerAlreadyEntered = true;
}
bool lockTaken = false;
try
{
Monitor.TryEnter(XXX_Conn.SerialPortLockObject, ref lockTaken);
if (!lockTaken)
throw new System.Exception("SerialOperationTimer " + name + ": Failed to get serial lock");
workerFunc(source, eventArgs);
}
finally
{
if (lockTaken)
{
Monitor.Exit(XXX_Conn.SerialPortLockObject);
}
workerAlreadyEntered = false;
}
}
public void Start()
{
Debug.Assert(Form1.GUIthreadHashcode == Thread.CurrentThread.GetHashCode());
Debug.Assert(!timer.Enabled);
Debug.WriteLine("SerialOperationTimer " + name + ": Start");
if (runningTimer != null)
{
Debug.Assert(false);
throw new System.Exception("SerialOperationTimer " + name + ": Attempted 'Start' while " + runningTimer.name + " is still running");
}
Monitor.Exit(XXX_Conn.SerialPortLockObject);
runningTimer = this;
timer.Enabled = true;
}
public void Stop()
{
Debug.Assert(Form1.GUIthreadHashcode == Thread.CurrentThread.GetHashCode());
Debug.Assert(timer.Enabled);
Debug.WriteLine("SerialOperationTimer " + name + ": Stop");
if (runningTimer != this)
{
Debug.Assert(false);
throw new System.Exception("SerialOperationTimer " + name + ": Attempted 'Stop' while not running");
}
timer.Enabled = false;
runningTimer = null;
if(Form1.xxConnection.PortIsOpen) Form1.xxConnection.CiCommDiscardBothBuffers();
bool lockTaken = false;
Monitor.TryEnter(XXX_Conn.SerialPortLockObject, 3000, ref lockTaken);
if (!lockTaken)
throw new Exception("Serial port lock not yet released by background timer thread "+name);
if (Form1.xxConnection.PortIsOpen)
{
int r = Form1.xxConnection.CiSync();
Debug.Assert(r == XXX_Conn.CI_OK);
if (r != XXX_Conn.CI_OK)
throw new Exception("Cannot re-sync with device after disabling timer thread " + name);
}
}
public static void StopAllBackgroundTimers()
{
if (runningTimer != null) runningTimer.Stop();
}
public double Interval
{
get { return timer.Interval; }
set { timer.Interval = value; }
}
}