我有一个特殊的问题。
我需要用来与串行设备通信的自定义协议如下工作,我正在使用标准的ASCII缩写。
如果消息是期望答案的请求,则设备发回数据。因为SerialPort.DataReceived
的工作方式,我必须手动将收到的消息拼凑回来。
它有点像这样:
这基本上就是这两个人沟通的方式,除了丢失的数据包和NAK以及那些东西,但我不想让它变得太复杂。
我的问题是:如何将完整消息的发送或接收完整的消息包装到原子操作中?我不想要的是我的程序发送新消息,而我正在将收到的消息粘在一起。
我尝试过使用Monitor.Enter()
和Monitor.Exit()
但是收到的事件是在另一个线程上调用的,所以没有骰子。
我还尝试使用只有1个资源的Semaphore
,在发送或接收开始时调用semaphore.WaitOne()
,并在发送EOT后调用semaphore.Release()
从设备收到EOT。这也行不通。
有没有办法更好地做到这一点?
答案 0 :(得分:1)
我会使用锁:
http://msdn.microsoft.com/en-gb/library/c5kehkcz(v=vs.71).aspx
您可以设置一个全局对象说serialLock并使用它来确保当您将各个部分粘合在一起时,任何发送线程都必须等待:
收到
lock(serialLock)
{
//Glue data
}
在所有发送中:
lock(serialLock)
{
// send data
}
这将对不同线程等的任何问题进行排序。
在释放锁定之前,我还要确保您已在胶水数据部分收到完整的消息。也许要清除它将需要更新DataReceived事件中的线程安全数据结构,并且在粘贴数据部分中,您需要在释放锁之前在此部分中检查完整消息的数据结构。 / p>
答案 1 :(得分:1)
当我编写代码以通过串口与GSM调制解调器通信时遇到类似的问题。
通过在System.Threading
命名空间中使用AutoResetEvent
类,我能够开发出一个相当不错的解决方案。基本上,我使用Send方法等待ACK信号。
这是一个骨架代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;
namespace Sample
{
public class SocketWrapper
{
#region Static Variables
private static AutoResetEvent _SendWaitHandle = new AutoResetEvent(false);
#endregion
#region Member Variables
private object _SendLockToken = new object();
#endregion
#region Public Methods
public void Write(byte[] data)
{
Monitor.Enter(_SendLockToken);
try
{
// Reset Handle
_SendWaitHandle.Reset();
// Send Data
// Your Logic
// Wait for ACK
if (_SendWaitHandle.WaitOne(1000)) // Will wait for 1000 miliseconds
{
// ACK Received
// Send EOT
}
else
{
// Timeout Occurred
// Your Logic To Handle Timeout
}
}
catch (Exception)
{
throw;
}
finally
{
Monitor.Exit(_SendLockToken);
}
}
#endregion
#region Private Methods
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// When ACK is received call SET
_SendWaitHandle.Set();
}
#endregion
}
}
注意:
在DataReceived方法中,请确保不要直接/间接调用Write方法,因为这可能会导致死锁。始终使用BackgroundWorker
或使用TPL在不同的线程上开始处理接收的数据。
答案 2 :(得分:1)
我将定义一个'SerialTxn'类,其中包含请求数据的成员,回复数据,编码/解码协议的状态机,超时,异常/ errorMessage,OnCompletion(SerialTxn thisTxn)事件/委托,以及将完整交易转换为一个类所需的任何其他内容。然后我会得到一个,用请求数据等加载它并将它(BlockingCollection)排队到一个处理串口的线程。线程获取实例,将其方法调用到tx和rx数据,并在完成后调用OnCompletion事件/委托。这允许同步和异步事务 - OnCompletion事件可以发信号通知事件(可能是AutoResetEvet),原始线程正在等待,或者它可以将回复排队回原始线程可以在其希望时处理的某个队列。 / p>
由于只有一个线程处理该端口,因此tx / rx操作重叠的可能性为零。 txn要么完全成功,要么在适当的异常或错误消息中加载到SerialTxn中。如果发生错误,或者需要将数据发送到记录器,GUI或其他任何内容,则所有相关数据都在一个对象中的一个位置,准备排队,BeginInvoked或其他任何内容。
我不喜欢锁定漫长的操作...