views:

63

answers:

3

I'm writing a TCP server, and at the very heart of it is a fairly standard bind-listen-accept piece of code nicely encapsulated by TcpListener. The code I'm running in development now works, but I'm looking for some discussion of the thread model I chose:

        // Set up the socket listener
        // *THIS* is running on a System.Threading.Thread, of course.
        tpcListener = new TcpListener(IPAddress.Any, myPort);
        tpcListener.Start();
        while (true)
        {
            Socket so = tpcListener.AcceptSocket();
            try
            {
                MyWorkUnit work = new MyWorkUnit(so);
                BackgroundWorker bw = new BackgroundWorker();
                bw.DoWork += new DoWorkEventHandler(DispatchWork);
                bw.RunWorkerCompleted += 
                                new RunWorkerCompletedEventHandler(SendReply);
                bw.RunWorkerAsync(work);
            }
            catch (System.Exception ex)
            {
                EventLogging.WindowsLog("Error caught: " + 
                                     ex.Message, EventLogging.EventType.Error);
            }
        }

I've seen good descriptions of which kind of thread model to pick (BackgroundWorker, stock Thread, or ThreadPool) but none of them for this kind of situation. A nice summary of the pros and cons of each is backgroundworker-vs-background-thread (second answer). In the sample code above, I picked BackgroundWorker because it was easy. It's time to figure out if this is the right way to do it.

These are the particulars of this application, and they're probably pretty standard for most transaction-like TCP servers:

  • Not a Windows Forms app. In fact, it's run as Windows Service.
  • (I'm not sure whether the spawned work needs to be a foreground thread or not. I'm running them as background now and things are okay.)
  • Whatever priority assigned to the thread is fine as long as the Accept() loop gets cycles.
  • I don't need a fixed ID for the threads for later Abort() or whatever.
  • Tasks run in the threads are short -- seconds at most.
  • Potentially lots of tasks could hit this loop very quickly.
  • A "graceful" way of refusing (or queuing) new work would be nice if I'm out of threads.

So which is the right way to go on this?

+3  A: 

For this main thread, use a separate Thread object. It is long running which is less suitable for the ThreadPool (and the Bgw uses the ThreadPool).

The cost of creating it doesn't matter here, and you (may) want full control over the properties of the Thread.

Edit

And for the incoming requests, you can use the ThreadPool (directly or through a Bgw) but note that this may affect your throughput. When all threads are busy there is a delay (0.5 sec) before an extra thread is created. This ThreadPool behaviour might be useful, or not. You can tweak MinThreads to control it somewhat.

It's crude but if you were to create your own threads for the spawned tasks you might have to come up with your own throttle mechanism.

It all depends on how much requests you expect, and how big they are.

Henk Holterman
Yup yup; any task that needs to be a separate thread and basically runs the whole duration of an application should ultimately be run on a full-fledged Thread instance. +1
Andrew Barber
I think you missed the point though. The main thread is running on a System.Threading.Thread (or might even be the main thread for the application) I added that to the code as a description. What kind of threads do I use for the _spawned off_ tasks?
clintp
@clint: Yes I did miss that. see edit.
Henk Holterman
+1  A: 

BackgroundWorker seems to be a decent choice here for your workers. My only caveat would be to make sure you aren't blocking for network traffic on those threads themselves. Use the Async methods for sending/receiving there, as appropriate, so that ThreadPool threads are not being blocked for network traffic.

It's fine (appropriate, really) for those threads to be Background threads, too. The only real difference is that under normal circumstances, a Foreground thread will keep a process alive.

Also I don't think you mentioned this main thread which captures these connections; that one is appropriate for a regular System.Threading.Thread instance.

Andrew Barber
The main thread is on a regular System.Treading.Thread instance. I didn't think that was relevant for the discussion so omitted it.
clintp
Gotcha; sounds good!
Andrew Barber
A: 

Well, personally none of those would be my first choice. I tend to prefer asynchronous IO operations by taking advantage of Socket.BeginReceive and Socket.BeginSend and let the underlying IO completion ports do all of the threading for you. But, if you would prefer to use synchronous IO operations then shuttling them off to the ThreadPool or a Task (if using .NET 4.0) would be the next best option.

Brian Gideon