Search code examples
c#multithreadingasynchronoustcpclientnetworkstream

Second Call to NetworkStream BeginRead() Massive Resource Contention


I am coding a server/client project with TcpClient and NetworkStream objects. I expect many clients to connect to the server, which are stored in a List<> of custom NetworkNode objects, each of which has a TcpClient and a NetworkStream for communication with the respective client.

The server needs to be able to maintain a connection to the client and wait for and action messages immediately (quickly) as received. Synchronous polling is very undesirable for this application, nor do I have any desire to code it that way.

Currently the server is asynchronously accepting clients and adding them to the List<>, which is working quite smoothly. I have tested this with a console app generating up to 100 clients and connecting to the server at the loopback address within a very short period of time (< 1sec).

When a client is added to the List<> the object uses the GetStream() method to return the client's NetworkStream object. I am attempting to use the NetworkStream.BeginRead() method to implement async data reception from each TCP client. The first call to the method is as:

this.Stream.BeginRead(readBuffer, readBufferOffset, readBuffer.Length - readBufferOffset, 
    new AsyncCallback(nodeStreamReadCallback), this.Stream);

Because the console test application sends some data as soon as it connects to the server, the object's readCallback(IAsyncResult) method is called almost immediately:

private void readCallback(IAsyncResult ar)
{
    NetworkStream _stream = (NetworkStream)ar.AsyncState;

    int _bytesRead = 0;

    _bytesRead = _stream.EndRead(ar);

    this.Stream.Write(readBuffer, readBufferOffset, _bytesRead);

    //increase buffer offset value
    readBufferOffset += _bytesRead;

    //TODO process the received data
    ...

    //wait for the next chunk of data
    this.Stream.BeginRead(readBuffer, readBufferOffset, readBuffer.Length - readBufferOffset, 
    new AsyncCallback(readCallback), this.Stream);
}

I am performing a second call to Stream.BeginRead() with the intention of waiting for the next block of data to arrive or become available, whenever in the future that might be.

When I comment out the second call to Stream.BeginRead() everything operates very smoothly. All the data is received and sent back to every client, without delay and with extremely minimal thread use (avg 2 to 3 additional threads during the process).

However - even if only a single client has connected, if I attempt to make a second call to Stream.BeginRead() within the readCallback() method (as above) I encounter massive contention issues. For a single client, CPU usage after the second call jumps from ~ 0% to between 30% and 60%, and the number of threads can jump from 11 to as many as 35.

So it's a threading or recursion issue, and I feel I should be waiting for something when I'm not however I can't quite wrap my head around what's going on here. This is the same pattern I've used with the TcpListener.BeginAcceptTcpClient() so I think it must operate differently.

I appreciate any and all advice that you might have to offer, and thanks in advance for your help!


Solution

  • Check your _bytesRead for 0, because that means your stream was closed on the remote side. Calling BeginRead on such a stream again would result directly in calling your callback with a count of read bytes of 0 again and again.