Why Block At All? Thoughts on threading and sockets

0 comments suggest edit

The path of least resistance when writing threading code as well as socket communications is to use techniques that cause indefinite blocking of some sort. Personally, I prefer never to block indefinitely. For example, it’s quite common to see code such as:

lock(someObject)
{
    //Do Something here...
}

Nothing wrong with this inherently, but this piece will try to acquire a lock on someObject indefinitely. Imagine if you mistakenly had code like (yes, it’s a bit contrived)

using System.Threading;

//... other stuff ...

object someObject = new Object();
object someOtherObject = new object();

public void LockItUp()
{
    lock(someObject)
    {
        Console.WriteLine("Lock 1 acquired.");

        ManualResetEvent mre = 
                new ManualResetEvent(false);
        WaitCallback callback = 
                new WaitCallback(ThrowAwayTheKey);        

        ThreadPool.QueueUserWorkItem(callback, mre);
        
        // wait till other thread has lock on 
        // someOtherObject
        mre.WaitOne(); 

        lock(someOtherObject)
        {
            Console.WriteLine("I never get called.");
        }
    }
}

void ThrowAwayTheKey(object resetEvent)
{
    lock(someOtherObject)
    {
        Console.WriteLine("Lock 2 acquired.");
        ManualResetEvent mre = 
                resetEvent as ManualResetEvent;
        if(mre != null)
            mre.Set(); //original thread can continue.

        lock(someObject)
        {
            Console.WriteLine("Neither do I");
        }
    }
}

Calling the method LockItUp will cause a deadlock and the application will hang until you kill it. Although this example is a bit contrived, you’d be surprised how easy it is in a sufficiently large and complicated system with multiple developers for you to run into this situation in a more roundabout manner. I see this often enough because using the lock statement is the path of least resistance. Instead, try using the TimedLock struct.

Another situation this type of thing comes up is with socket programming. Often I see code like this:

using System.Net.Sockets;

//... other stuff ...

byte[] _buffer = new byte[4096];
public void Listen(Socket socket)
{
    int bytesRead 
        = socket.Receive(_buffer, 0, 4096
                , SocketFlags.None);

    //You're sitting here all day.
}

If the remote socket isn’t forthcoming with that data, you’re going to be sitting there all day holding that thread open. In order to stop that socket, you’ll need another thread to call Shutdown or close on the socket. Contrast that with this approach:

byte[] _buffer = new byte[4096];
public void BeginListen(Socket socket)
{
    socket.BeginReceive(_buffer, 0, 4096
            , SocketFlags.None
            , new AsyncCallback(OnDataReceived)
            , socket);
    //returns immediately.
}

void OnDataReceived(IAsyncResult ar)
{
    Socket socket = ar.AsyncState as Socket;
    int bytesRead = socket.EndReceive(ar);
    
    //go on with your bad self...
}

BeginListen returns immediately and OnDataReceived isn’t called until there’s actual data to receive. An added benefit is that you’re not taking up a thread from the ThreadPool, but rather you’re using IO completion ports. IO Completion ports is a method Windows uses for asynchronous IO operations. When an asynchronous IO is complete, Windows will awaken and notify your thread. The IO operations run on a pool of kernel threads whose only task in life is to process I/O requests.

Since BeginListen returns immediately, you’re free to close the socket if no data is received after a certain time or in response to some other event. This may be a matter of preference, but this is a more elegant and scalable approach to sockets.

For more on asynchronous sockets, take the time to read Using an Asynchronous Server Socket and related articles.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

One response

  1. Avatar for Ian Griffiths
    Ian Griffiths August 8th, 2004

    One minor niggle with this code...



    Although the example is correct as it stands, it doesn't mention an important issue: the Socket class is not thread-safe. This means that if you do use the async operations (and by the way, I'm completely with you here - I'm a big fan of the async operations) you need to take steps to synchronize access to the socket.



    As it stands there's nothing wrong with this example as far as I can see. But what if you also have an asynchronous read operation outstanding? Can you guarantee that a read and a send won't complete simultaneously, and that you'll be trying to access the socket from both completion handlers simultaneously.



    So in practice, you tend to want to use some kind of locking to guarantee that your socket is only being used from one thread at a time, once you start using async socket IO.



    (Also, you left out one of the clever parts of IO completion ports - the scheduler tracks which threads are associated with work from an IO port, and tries to make sure that you have exactly as many running as you have CPUs. If one of the threads handling work from an IO completion port blocks, the OS will release another work item from the completion port. Conversely, if loads of IO operations complete simultaneously, it only lets them out of the completion port as fast as your system can handle them, and no faster - this avoids swamping the scheduler under high load.)