C# System.IO.Ports.SerialPort 和多线程
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/973631/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
System.IO.Ports.SerialPort and Multithreading
提问by TimothyP
I have some SerialPort code that constantly needs to read data from a serial interface (for example COM1). But this seems to be very CPU intensive and if the user moves the window or a lot of data is being displayed to the window (such as the bytes that are received over the serial line) then communication gets messed up.
我有一些 SerialPort 代码,它们经常需要从串行接口(例如 COM1)读取数据。但这似乎非常占用 CPU,如果用户移动窗口或向窗口显示大量数据(例如通过串行线路接收的字节),那么通信就会混乱。
Considering the following code:
考虑以下代码:
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] buffer = new byte[port.ReadBufferSize];
var count = 0;
try
{
count = port.Read(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
if (count == 0)
return;
//Pass the data to the IDataCollector, if response != null an entire frame has been received
var response = collector.Collect(buffer.GetSubByteArray(0, count));
if (response != null)
{
this.OnDataReceived(response);
}
The code needs to be collected as the stream of data is constant and the data has to be analyzed for (frames/packets).
由于数据流是恒定的并且必须分析数据(帧/数据包),因此需要收集代码。
port = new SerialPort();
//Port configuration code here...
this.collector = dataCollector;
//Event handlers
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
port.Open();
If there is no user interaction and nothing being added to the window, this works fine but as soon as there is interaction communication really gets messed up. Timeouts occur etc....
如果没有用户交互并且没有向窗口添加任何内容,这可以正常工作,但是一旦有交互,通信就真的变得一团糟。发生超时等....
For example, this messes everything up:
例如,这把一切都搞砸了:
Dispatcher.BeginInvoke(new Action(() =>
{
var builder = new StringBuilder();
foreach (var r in data)
{
builder.AppendFormat("0x{0:X} ", r);
}
builder.Append("\n\n");
txtHexDump.AppendText(builder.ToString());
txtHexDump.ScrollToEnd();
}),System.Windows.Threading.DispatcherPriority.ContextIdle);
});
But even simple calls to log4net cause problems.
但即使是对 log4net 的简单调用也会导致问题。
Are there any best practices to optimize SerialPort communication or can someone tell me what I'm doing wrong...
是否有优化 SerialPort 通信的最佳实践,或者有人可以告诉我我做错了什么......
Update:
更新:
In case the above didn't make much sence. I made a very simple (and stupid) little example:
如果以上没有多大意义。我做了一个非常简单(和愚蠢)的小例子:
class Program
{
static void Main(string[] args)
{
var server = new BackgroundWorker();
server.DoWork += new DoWorkEventHandler(server_DoWork);
server.RunWorkerAsync();
var port = new SerialPort();
port.PortName = "COM2";
port.Open();
string input = "";
Console.WriteLine("Client on COM2: {0}", Thread.CurrentThread.ManagedThreadId);
while (input != "/quit")
{
input = Console.ReadLine();
if (input != "/quit")
{
var data = ASCIIEncoding.ASCII.GetBytes(input);
port.Write(data, 0, data.Length);
}
}
port.Close();
port.Dispose();
}
static void server_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Listening on COM1: {0}", Thread.CurrentThread.ManagedThreadId);
var port = new SerialPort();
port.PortName = "COM1";
port.Open();
port.ReceivedBytesThreshold = 15;
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
}
static void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
int count = 0;
byte[] buffer = new byte[port.ReadBufferSize];
count = ((SerialPort)sender).Read(buffer, 0, buffer.Length);
string echo = ASCIIEncoding.ASCII.GetString(buffer,0,count);
Console.WriteLine("-->{1} {0}", echo, Thread.CurrentThread.ManagedThreadId);
}
}
The result might look like this:
结果可能如下所示:
Listening on COM1: 6 Client on COM2: 10 This is some sample data that I send ---> 6 This is some sample data that I send
在 COM1 上侦听:6 COM2 上的客户端:10 这是我发送的一些示例数据 ---> 6 这是我发送的一些示例数据
So reading the data from the port happens on the main thread....
所以从端口读取数据发生在主线程上......
Might this be part of what's causing my problems?
这可能是导致我出现问题的原因的一部分吗?
采纳答案by Henk Holterman
Your last conclusion, that the event runs on the Main thread, may not be true for Windows App. Don't test this in a Console.
您的最后一个结论,即事件在主线程上运行,对于 Windows 应用程序可能不正确。不要在控制台中测试这个。
The proper way to tune this is:
调整这个的正确方法是:
set a large enough buffer, although the minimum 4096 is usually Ok
set the ReceivedBytesThreshold as high as tolerable (and do it beforethe Open())
do as little as possible in the Received event, pass the data to a Queue or MemoryStream if you need more time
设置一个足够大的缓冲区,虽然最小 4096 通常是好的
将 ReceivedBytesThreshold 设置为可容忍的高(并在 Open()之前进行)
在 Received 事件中尽可能少做,如果需要更多时间,将数据传递给 Queue 或 MemoryStream
回答by Gordon Freeman
You should rewrite port_DataReceived procedure to read data until port.BytesToRead is greater then zero, like this:
您应该重写 port_DataReceived 过程以读取数据,直到 port.BytesToRead 大于零,如下所示:
private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
while (port.BytesToRead > 0)
{
int byte_count = port.BytesToRead;
byte[] buffer = new byte[byte_count];
int read_count = port.Read(buffer, 0, byte_count);
// PROCESS DATA HERE
}
}
Also I would recommend you to just insert data in queue list in procedure port_DataReceived, and perform data processing in separate thread.
另外,我建议您只在过程 port_DataReceived 的队列列表中插入数据,并在单独的线程中执行数据处理。
回答by Paul Nathan
The classic solution is to have a FIFO buffer. Ensure that the size of the FIFO is large enough to handle any critical case where there is a lot of input and the processor block is being taken up.
经典的解决方案是使用 FIFO 缓冲区。确保 FIFO 的大小足够大以处理任何输入大量且处理器块被占用的关键情况。
You could even have a 2-buffer system:
你甚至可以有一个 2 缓冲系统:
--->|Reader|-->FIFO-->|Processor|--->FIFO2--->|Displayer|
回答by dave
I am surprised no one caught this. The SerialPort class utilizes its own thread when using the DataReceived event. This means that if the subscriber, for example, is accessing any Form elements, then it must be done so with either an Invoke, or BeginInvoke method(s). Otherwise, you end up with a cross thread operation. In older versions of .net, this would go along unnoticed with unpredictable behaviour (depending on the CPU cores in the PC) and in later versions, should raise an exception.
我很惊讶没有人发现这一点。SerialPort 类在使用 DataReceived 事件时利用自己的线程。这意味着,例如,如果订阅者正在访问任何 Form 元素,那么它必须使用 Invoke 或 BeginInvoke 方法来完成。否则,您最终会进行跨线程操作。在旧版本的 .net 中,这会伴随着不可预测的行为(取决于 PC 中的 CPU 内核)而被忽视,并且在更高版本中,应该引发异常。