我正在编写一个程序来模拟通过串口传输数据的设备。为此,我在表单中创建了一个System.IO.Ports.SerialPort对象,并在给定频率下创建了一个System.Windows.Forms.Timer进行传输。一切正常,但随着频率接近串口速度的限制,它开始锁定UI,并且当数据发送速度快于端口数据速度时,最终变得无响应。我的代码是:
private void OnSendTimerTick(object sender, EventArgs e)
{
StringBuilder outputString = new StringBuilder("$", 51);
//code to build the first output string
SendingPort.WriteLine(outputString.ToString());
outputString = new StringBuilder("$", 44);
//code to build the second output string
SendingPort.WriteLine(outputString.ToString());
if (SendingPort.BytesToWrite > 100)
{
OnStartStopClicked(sender, e);
MessageBox.Show("Warning: Sending buffer is overflowing!");
}
}
我期待WriteLine函数是异步的 - 当端口在后台传输时立即返回。相反,OnSendTimerTick函数似乎是正确的线程,但WriteLine似乎在UI线程中运行。
如何让串口以这种方式运行?在计时器中创建SerialPort对象似乎是一个坏主意,因为那时我必须在每个计时器刻度上打开和关闭它。
答案 0 :(得分:2)
它只是部分异步,它会立即返回,但只要你编写的字节适合串口驱动程序的发送缓冲区。当你用太多的数据充斥端口时,这将会突然停止,比它传输的速度快。您可以使用SerialPort.BaseStream.BeginWrite()方法使其真正异步。这并没有使它更快,但将瓶颈移到其他地方,可能远离用户界面。
答案 1 :(得分:0)
如果你创建了一个要写入的字符串队列,并且有一个后台线程从队列写入串口,那么这可以解决(UI的缓慢)。如果采用这种方法,请注意队列的大小。
编辑:出于某种原因我不能使用添加评论,所以我只是编辑它。 BeginWrite的文档有这样的声明“流上的BeginWrite的默认实现同步调用Write方法,这意味着Write可能会阻塞某些流。”然后它继续排除文件和网络流,但不包括SerialPort。我想你可以尝试看看。
答案 2 :(得分:0)
如果您正在使用System.Windows.Forms.Timer
,则会在UI线程上执行计时器事件处理程序。如果您在System.Timers.Timer
设置为表单时使用SynchronizingObject
,情况也是如此。如果串口缓冲区填满,则线程必须等到它有足够的空间来容纳你想要发送的新数据。
我建议您使用System.Threading.Timer
来执行此操作。在池线程上调用计时器回调,这意味着如果WriteLine
必须等待,UI线程将不会锁定。如果你这样做,那么你必须确保只有一个线程在任何时候执行定时器回调。否则,您可以不按顺序获取数据。这样做的最好方法是让计时器一次性完成并在每次回调结束时重新初始化它:
const int TimerFrequency = 50; // or whatever
System.Threading.Timer timer;
void InitTimer()
{
timer = new System.Threading.Timer(TimerCallback, null, TimerFrequency, Timeout.Infinite);
}
void TimerCallback(object state)
{
// do your stuff here
// Now reset the timer
timer.Change(TimerFrequency, Timeout.Infinite);
}
将有效的Timeout.Infinite
作为period
参数传递可防止计时器成为定期计时器。相反,它只发射一次。 Timer.Change
在每次发送后重新初始化计时器。
处理此问题的一种可能更好的方法是通过将WriteBufferSize
设置为足够大的值来完全取消计时器。然后你的程序可以将所有数据转储到缓冲区中,让SerialPort
实例担心通过网络运行它。当然,这假定您可以创建一个足够大的缓冲区来容纳程序尝试发送的任何内容。