My BeginRead never calls my callback function, please help!
So you have decided to do some awesome async network communication and you have settled about using NetworkStream, in System.Net.Sockets namespace. Perhaps the most important reason to choose NetworkStream over other encapsulations of socket communication is the fact that NetworkStream “is a” Stream and you can use it in any method that expects a Stream and it will work. This way you can write code that reads from or writes data to a System.IO.Stream such as FileStream, NetworkStream, etc. without knowing what kind of stream you will be working with, clairvoyant style.
Armed with this knowledge, you write the following code, where stream is an instance of NetworkStream:
stream.BeginRead(ti.data, 0, ti.data.Length, new AsyncCallback(ReadDataCallback), ti);
First things first: ti is the all encapsulating object of user defined type TaskInfo. It can be any type (or even null) as BeginRead expects an Object which is the mother of all types. ti is used to pass data to BeginRead and a reference to the same object is returned to the user in the callback function, in this case ReadDataCallback. We do this basically to pass data to our callback function. In case of passing null, we would get the same null reference back. We then would be clueless as to which connection this read operation belonged to. Not a good thing! Not in real life at least.
The callback function for BeginRead needs to have the signature:
void ReadDataCallback(IAsyncResult result);
Where result can be cast to whatever the real type is in the body of this callback function and is a reference to the ti we passed to BeginRead in the first place.
With that out of the way, BeginRead immediately returns and registers ReadDataCallback to be called when the read operation on the NetworkStream completes. But it just so happens that this function does not have a timeout and there will be cases where you will never hear from this read attempt again. This may be fine in some cases, where there simply wasn’t any more data but consider this scenario: You are trying test your communication with 100 clients at a time, and you would like to know the current number of connections. If BeginRead never calls your callback function, how could you ever know if that client is active or not and restart a new one if it is not there anymore? In this case, the solution I use is registering my own timer and embedding it into ti:
ti.stateTimer = new Timer(new TimerCallback(TimerCallback), ti, TIMEOUT, Timeout.Infinite);
stateTimer is a member of ti and is of type System.Threading.Timer. The trick here is we will do this before passing ti to BeginRead method. This way we are passing our ti object to the async function with a ticking bomb! As soon as TIMEOUT is reached, our TimerCallback function will be called by the threading system, and there we can perform cleanup.
One thing I have not mentioned here, which perhaps is going to cause you a lot of headache down the road if you don't pay attention to is the synchronization of these two callback functions. Anyone who has worked with multithreaded applications for more than 5 minutes knows well that if there is a code path that will lead to a race condition, you will always hit it. Continuing the example before, you have two callback functions which are eager to decrement the number of clients either through success or failure and there will be cases where both can be called at the same time. In this case you will have a double decrement. Or the timer will fire right after or before the ReadDataCallback and now you will close the connection and cleanup in the middle of one of your callbacks. Disaster! The two needs to somehow communicate. The fact that ti is shared among these once again comes to rescue. This is the complete TimerCallback function:
private void TimerCallback(object result) { TaskInfo ti = result as TaskInfo; // If the timer was OK, then make it not OK and close client if (Interlocked.Exchange(ref ti.timerOK, 0) == 1) { Interlocked.Increment(ref timerCallbackCalled); CloseClient(ti); } }
As you can see, ti contains a synchronization primitive, which is just an integer and it is aptly called timerOK. If timer is OK, which means it is still alive, we call CloseClient, which is the cleanup function where we decrement the number of clients. If the timer on the other is not OK, we simply exit. We need a similar piece of code for ReadDataCallback:
private void ReadDataCallback(IAsyncResult result) { TaskInfo ti = result.AsyncState as TaskInfo; // If the timer was OK, then make it not OK and continue to reuse ti later. if (Interlocked.Exchange(ref ti.timerOK, 0) == 1) { // Execution goes here } }
In this case, if the timer is OK, we disable it and continue our execution and optinally cleanup or simply keep using the connection. If the timer is NOT OK, then it means it beat us to it, in which case we don't do anything. Timer callback codepath will cleanup the state for us. And with this you now can use BeginRead without worrying about ghost reads for which no callback ever occurs.