5

我在 C# .Net 4.0 应用程序中遇到了一个有趣SerialPortThreadPool.QueueUserWorkItem问题Task

仅当我同时使用 2 个或更多 SerialPorts 时才会出现此问题。每个串行端口在我以 3 种方式中的 1 种方式创建的自己的线程中运行:

  1. new Thread(DoSerialCommX)
  2. ThreadPool.QueueUserWorkItem(DoSerialCommX)
  3. 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,一切都很好。另外,我应该提到这似乎只发生在基于 Intel Atom 的 PC 上。台式电脑似乎没有问题。

我知道线程池重用线程,并且默认Task使用线程池。而且我知道线程池确实适用于短期操作。但是我尝试使用TaskCreationOptions.LongRunning,我认为它会产生一个专用线程而不是使用线程池,但它仍然不起作用。

所以问题是:在这种情况下有 什么Thread特别之处?有什么东西Thread使它更适合 IO 操作吗?

编辑: 到目前为止的答案似乎假设我正在尝试将 ThreadPool 或 Tasks 用于永无止境的过程。在我的实际应用中,情况并非如此。我只是在上面的代码中使用了一个永无止境的循环来说明问题。我真的需要知道为什么Thread有效和ThreadPool无效Task。它们在技术上有何不同会导致串行通信中断?

4

4 回答 4

2

从哲学上讲,执行线程的行为方式在 1、2 和 3 之间几乎没有区别。除非您覆盖它们,否则它们都共享相同的默认执行优先级 - 从理论上讲,线程调度程序将使用相同的策略来调度它们。他们都会快乐地坐在圈子里旋转。

我怀疑方法之间更大的区别是:

  1. 基础设施成本(线程、内存等)旋转起来以支持 #2 和 #3 的线程池,所有这些都将与您的执行循环竞争时钟时间片。
  2. 肌肉较少的 Atom 上的线程上下文切换成本。Atom 具有更小的缓存和更短的处理管道。更多运行线程 = 更多上下文切换、更多流水线转储和降低指令缓存效率。

从功能的角度来看,使用方法 2 和 3 有点滥用 - 您的意图是永远不要退出该方法。这些策略针对原子的、有限的操作进行了优化,并且有些无指导的执行通常适用于异步网络、磁盘操作等 IO 完成端口任务......(也许您的串行端口代码也有可能?)

除非您有兴趣适应 Async IO,否则我会跳过 2 和 3。专注于线程 - 似乎您想要更精细、可预测的执行控制,而不需要 Threadpool 带来的基础设施开销。

于 2012-04-12T04:28:25.247 回答
1

您的代码、驱动程序或硬件存在问题,使您的串行端口性能“处于边缘”,以至于它一直处于无法正常工作的边缘。使用专用线程或线程池应该没有问题(mod。使用线程池线程阻塞串行端口读取)。

两个 9600 波特的串行端口可以用算盘运行,前提是珠子​​和电线都上油了。

于 2012-04-12T09:56:36.653 回答
1

我怀疑这是因为您正在从 COM 端口读取/写入。如果没有这个因素,所有这些都应该执行相同的操作,因为(如果您检查的话)它们都在具有 Normal 优先级的线程中运行。

也许阅读I/O 完成端口和线程池,看看是否可以解释这种奇怪的行为。

于 2012-04-12T07:00:37.873 回答
0

有细微的差别 - 这导致了超时:

  • 在调用 Thread.Start() 时,使用 Thread 显式创建一个新线程 - 并确保您的代码在该特定时刻运行。
  • 使用 ThreadPool 可确保根据内部 ThreadPool 逻辑在不久的将来打开一个线程。由于 ThreadPool 数量有限,建议不要将其用于长时间(或无限)运行操作。
  • 使用任务只会确定代码会运行。无论是在另一个线程上,还是在主线程上,都不能确保它会立即运行,或者不同的任务会在不同的线程上运行。

因此,如果您希望您的任务快速启动,请始终使用 Thread。Tasks 和 ThreadPool 都有一些额外的“基础设施开销”,可能会导致您注意到的轻微延迟。

由于 ThreadPool 和 task 都不打算在您的任务不存在时使用,我建议您使用 Thread。

于 2012-04-12T04:39:17.837 回答