views:

401

answers:

3

My C# application is such that a background worker is being used to wait for the acknowledgement of some transmitted data. Here is some psuedo code demonstrating what I'm trying to do:

UI_thread
{
   TransmitData()
   {
      // load data for tx
      // fire off TX background worker
   }

   RxSerialData()
   {
      // if received data is ack, set ack received flag
   }
}

TX_thread
{
   // transmit data
   // set ack wait timeout
   // fire off ACK background worker
   // wait for ACK background worker to complete
   // evaluate status of ACK background worker as completed, failed, etc.
}

ACK_thread
{
   // wait for ack received flag to be set
}

What happens is that the ACK BackgroundWorker times out, and the acknowledgement is never received. I'm fairly certain that it is being transmitted by the remote device because that device has not changed at all, and the C# application is transmitting. I have changed the ack thread from this (when it was working)...

for( i = 0; (i < waitTimeoutVar) && (!bAckRxd); i++ )
{
   System.Threading.Thread.Sleep(1);
}

...to this...

DateTime dtThen = DateTime.Now();
DateTime dtNow;
TimeSpan stTime;

do
{
   dtNow = DateTime.Now();
   stTime = dtNow - dtThen;
}
while ( (stTime.TotalMilliseconds < waitTimeoutVar) && (!bAckRxd) );

The latter generates a very acurate wait time, as compared to the former. However, I am wondering if removal of the Sleep function is interferring with the ability to receive serial data. Does C# only allow one thread to run at a time, that is, do I have to put threads to sleep at some time to allow other threads to run?

Any thoughts or suggestions you may have would be appreciated. I am using Microsoft Visual C# 2008 Express Edition. Thanks.

+3  A: 

To answer the direct question of whether or not C# allows one thread to run at a time, C# doesn't have anything to do with threading. The .NET framework, however, will allow you to run multiple (logical) threads at a time. How that is actually handled is a function of the framework and the OS.

As for your problem, I think you have too many threads in a wait state to begin with. Your methods to send and receive the data should have async call models on them (Begin and End). Because of this, you should start your transmission with a call to Begin and then attach a callback which is called when the function terminates.

Then, in the callback, you would process the result and proceed to the next async operation (if necessary) or update the UI.

casperOne
+3  A: 

Your "new" version of RX thread is utilizing 100% of processor time - it just runs continuously and never sleeps. Besides the general evil nature of this approach, this might (although not certainly) prevent some other things, like receiving data, from happening on time.

For wait scenarios like this, one would usually utilize a thread synchronization construct called event. You create an "event" object, and the RX thread waits on it, while the processing thread signals the event when the ACK is received.

AutoResetEvent event = new AutoResetEvent( false );

// ...
// ACK waiting thread:
event.WaitOne();
// ...

// ...
// Whatever thread actually receives the ACK
if ( /* ack received */ )
{
    // bAckRxd = true; - comment this out. Replace with following:
    event.Set();
}

As for why exactly you're not receiving your ACK, I need more information. What exactly is the channel? Is it a serial port? Network? Pipe? Something else?

Fyodor Soikin
Thanks, Fyodor. That seems to do the trick. I am using serial communications.
Jim Fell
Glad it helped. You're welcome. :-)
Fyodor Soikin
+1  A: 

Your code is rather thread-happy. That can indeed get you in trouble when you expect them to time stuff. Especially when you use BGW or thread pool threads, the scheduler only allows them to run when there are not more threads active than you have CPU cores. Or when threads get "stuck" for a while. Yours get stuck. You also don't seem to use them effectively, polling loops burn a lot of needless CPU cycles.

Leverage the capabilities of the SerialPort class to avoid this:

  • You don't need a transmit thread. The serial port driver has a buffer, your Write() call will instantly return when the data fits the buffer. Writing from the main thread is fine.
  • You don't necessarily need a receive thread. Serial port already has one, it runs the DataReceived event. It can bump a Timer that you started when you transmitted the data.
  • SerialPort already has a ReadTimeout property. You can use it in a receive thread to timeout the Read() call.

Sleep() doesn't interfere with serial ports, their drivers read data using hardware interrupts.

Hans Passant
Yes, I agree. I eliminated 2 of the 4 background workers and implemented the AutoResetEvent suggested by Fyodor. I'll have to take a closer look at the SerialPort class. Thanks.
Jim Fell