After spending way more time than seems reasonable to find an answer to this simple question, I thought I would leave my results here so others don't have to jump through all the hoops and wrong paths that I had just followed.
The problem is that if you use the TcpClient ReadTimeout property and your Read operation actually times out, Microsoft decided to close the socket. This is not expected, not desirable, not done by any other socket implementation I know of, and has no valid reason that this should be the case other than programmer laziness. But this is what Microsoft chose to do.
Anyways, all the workarounds I found, including on this site, had various ways of doing some form of busy polling and some even involved kicking off yet another thread to perform a simple Read call. I'm sorry, but I've got better things to do with my CPU than to sit there and busy poll, especially with many sockets open, so this is not an option for me. After all, this isn't the early 1990's where busy polling was just how you did things. Nowadays, we have this thing called operating systems that take care of these types of things quite efficiently using interrupts.
Anyways, on yet another tangential search I stumbled upon this old blog post:
MSDN Blogs > Mike Flasko's Blog > Handling a timeout when reading network data
The key take aways that tell you the solution on how to properly handle read timeouts are:
At this point one may be tempted to catch the exception and then reissue the read on the same NetworkStream. This strategy can lead to unexpected errors. The best thing to do is to now treat the NetworkStream (socket) as being in an unstable state. This is because when the underlying stack times out, the underlying I/O read gets cancelled. If data comes in at the same time, the data will be lost, resulting in a corrupted data stream.
and the solution:
A better approach is to catch the exception, close the socket or TCPClient and reconnect if necessary.
While I still think this puts unnecessary burden on the user of the API, at least it is the most proper solution I was able to find out of the dozens of sites I looked at trying to figure out to do a semi-proper socket ReadTimeout.
I hope this question/comment saves somebody the hours it took me to find.