Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Thread vs ThreadPool vs Task for SerialPort Communication

I've run into an interesting problem in my C# .Net 4.0 application using the SerialPort class and either ThreadPool.QueueUserWorkItem or Tasks.

The problem only occurs if I use 2 or more SerialPorts simultaneously. Each serial port runs in its own thread that I create in 1 of 3 ways:

  1. new Thread(DoSerialCommX)
  2. ThreadPool.QueueUserWorkItem(DoSerialCommX)
  3. new Task(DoSerialCommX, TaskCreationOptions.LongRunning).Start()

To illustrate the problem, I created my DoSerialCommX method to read and write to the serial port forever in a loop. It looks something like this: (I'm not actually doing this in my real program. This is just a snippet from my test program that isolates and illustrates the problem).

private void DoSerialCommX()
{
    SerialPort port = new SerialPort("ComX", 9600);
    port.Open();

    while(true)
    {
        //Read and write to serial port
    }
}

If I use either method 2 or 3, the serial communication stutters and I receive many communication timeouts. If I use method 1, all is good. Also, I should mention this only seems to occur on my Intel Atom based PCs. Desktop PC's seem to have no problems.

I know the thread pool reuses threads, and by default Task uses the thread pool. And I know that the thread pool really intended for short-lived operations. But I tried using the TaskCreationOptions.LongRunning, which I thought would spawn a dedicated thread rather than use the thread pool, but it still didn't work.

So the Question: What makes Thread so special in this situation? Is there something about Thread that makes it better suited for IO operations?

Edit: The answers so far seem to assume that I am trying to use the ThreadPool or Tasks for a never-ending process. In my real application this is not the case. I'm only using a never-ending loop in the code above to illustrate the problem. I really need to know why Thread works and ThreadPool and Task don't. What is technically different about them that would cause the serial communication to hiccup?

like image 958
Verax Avatar asked Oct 08 '22 03:10

Verax


2 Answers

Philosophically there is little difference between 1, 2 and 3 in how executing thread behaves. They all share the same default execution priority unless you override it - notionally the thread scheduler would use the same strategy to schedule them. They'd all sit happily spinning in the loop.

I suspect the bigger difference between methods is:

  1. infrastructure cost (threads, memory etc.) spun up to support threadpool of #2 and #3 all of which will contend for clock timeslices with your execution loop.
  2. thread context switching cost on the less-muscular Atom. The Atom has a smaller caches, and shorter processing pipeline(s). More running threads = more context switches, more dumping of pipeline(s) and reduced instruction cache efficiency.

From a functional point of view use of Method 2 and 3 are somewhat abusive - your intention is to never exit the method. These strategies are optimized for atomic, finite operations, and somewhat unguided execution oft suited for IO Completion port tasks such as async network, disk operations etc... (Maybe a possibility for your serial port code too?)

I would skip 2 & 3 unless you're interested in adapting to Async IO. Focus on thread - seems like you're wanting finer grain, predictable execution control without the infrastructure overhead that Threadpool brings.

like image 145
stephbu Avatar answered Oct 12 '22 10:10

stephbu


There is something off with either your code, your driver or your hardware that has put your serial port performance so 'on the edge' that it is on the verge of not working all the time. There should be no problem using a dedicated thread or the threadpool, (mod. using up a threadpool thread to block on a serial port read).

Two 9600-baud serial ports you can run with an abacus, providing the beads and wires are well oiled.

like image 41
Martin James Avatar answered Oct 12 '22 12:10

Martin James