我在使用SerialPort
类以及ThreadPool.QueueUserWorkItem
或Task
的C#.Net 4.0应用程序中遇到了一个有趣的问题。
如果我同时使用2个或更多SerialPort,则只会出现此问题。每个串口都在自己的线程中运行,我以3种方式之一创建:
new Thread(DoSerialCommX)
ThreadPool.QueueUserWorkItem(DoSerialCommX)
new Task(DoSerialCommX, TaskCreationOptions.LongRunning).Start()
为了说明这个问题,我创建了DoSerialCommX
方法,以便在循环中永久地读取和写入串行端口。它看起来像这样:(我实际上并没有在我的真实程序中执行此操作。这只是我的测试程序中的一个片段,它隔离并说明了问题)。
private void DoSerialCommX()
{
SerialPort port = new SerialPort("ComX", 9600);
port.Open();
while(true)
{
//Read and write to serial port
}
}
如果我使用方法2或3,则串行通信会断断续续,并且会收到许多通信超时。如果我使用方法1,一切都很好。此外,我应该提到这似乎只发生在我的基于英特尔凌动的PC上。台式电脑似乎没有任何问题。
我知道线程池重用了线程,默认情况下Task
使用线程池。我知道线程池真正用于短期操作。但我尝试使用TaskCreationOptions.LongRunning
,我认为它会产生一个专用线程,而不是使用线程池,但它仍然无效。
所以问题:在这种情况下Thread
如此特别的原因是什么?是否有关于Thread
的内容使其更适合IO操作?
修改
到目前为止,答案似乎假设我正在尝试使用ThreadPool或任务进行永无止境的过程。在我的实际应用中,情况并非如此。我只是在上面的代码中使用了一个永无止境的循环来说明问题。我真的需要知道为什么Thread
有效,ThreadPool
和Task
没有。什么在技术上有所不同会导致串行通信打嗝?
答案 0 :(得分:2)
哲学上,执行线程的行为方式在1,2和3之间几乎没有区别。它们都共享相同的默认执行优先级,除非您覆盖它 - 从概念上讲,线程调度程序将使用相同的策略来安排它们。他们都快乐地坐在旋转中。
我怀疑方法之间的差异更大:
从功能的角度来看,方法2和3的使用有点滥用 - 你的意图是永远不要退出方法。这些策略针对原子,有限操作以及适用于IO完成端口任务(如异步网络,磁盘操作等)的某些非制导执行进行了优化(也可能是您的串行端口代码的可能性?)
我会跳过2& 3,除非您有兴趣适应Async IO。专注于线程 - 似乎你想要更精细的粒度,可预测的执行控制,而没有Threadpool带来的基础架构开销。
答案 1 :(得分:1)
我怀疑发生这种情况,因为您正在从COM端口读取/写入。如果没有这个因素,所有这些都应该完全相同,因为(如果你检查)它们都在普通优先级的线程中运行。
也许请阅读I/O completion ports和线程池,看看是否可以解释这种奇怪的行为。
答案 2 :(得分:1)
您的代码,驱动程序或硬件都会使您的串口性能“处于边缘”,以至于它一直处于不工作状态。使用专用线程或线程池应该没有问题,(mod。使用线程池线程阻塞串口读取)。
两个9600波特串口,你可以用算盘运行,提供珠子和电线都很好。
答案 3 :(得分:0)
存在轻微差异 - 导致超时:
因此,如果您希望您的任务快速启动,请始终使用Thread。任务和ThreadPool都有一些额外的基础设施开销"这可能会导致你注意到的轻微延迟。
由于当您的任务从未存在时,并不打算使用ThreadPool和任务,我建议您使用Thread。