How to End NetworkStream.Read() safely ? (in C# or VB.net)

16,460

Solution 1

From what I can see when running your program, your 1st exception "Unable to read data from the transport connection..." is not thrown by the Stream.Read but by the TcpClient's constructor and is most probably due to the fact that there is no server accepting connections on 127.0.0.1:80.

Now if you want to end nicely on the Stream.Read, I would do it asynchronously. In this way you can catch any exceptions thrown during the Read and clean you program accordingly. Here is a modified version of your program:

using System;
using System.Net.Sockets;

namespace Stream
{
    class Program
    {
        private static TcpClient client;
        private static NetworkStream stream;

        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        void Run()
        {
            try
            {
                client = new TcpClient("127.0.0.1", 80);
                stream = client.GetStream();
                byte[] buffer = new byte[64];

                stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnRead), buffer);

                client.Close();
                Console.ReadKey();
            }
            catch (Exception)
            {
                //...
            }
        }

        void OnRead(IAsyncResult result)
        {
            try
            {
                stream.EndRead(result);
                byte[] buffer = result.AsyncState as byte[];

                if (buffer != null)
                {
                    //...
                }

                // continue to read the next 64 bytes
                buffer = new byte[64];
                stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnRead), buffer);
            }
            catch (Exception)
            {
                // From here you can get exceptions thrown during the asynchronous read
            }
        }
    }
}

Solution 2

  1. To avoid blocking, only call Read() when DataAvailable is true.
  2. The ThreadPool isn't suited for long-running tasks, so refactor your while loop
  3. You close the connection before you read from it (as the work item is executed async).

This might help

     void Run() {
        ThreadPool.QueueUserWorkItem(ReadLoop);
     }

     void ReadLoop(object x) {
         if (stream.DataAvailable) 
           stream.Read(new byte[64], 0, 64);
         else 
             Thread.Sleep(TimeSpan.FromMilliseconds(200));

         if (Finished)
               client.Close();
         else if (!Disposed && !Finished) 
               ThreadPool.QueueUserWorkItem(ReadLoop);
     }

You'll need to manage the Finished & Disposed yourself in your real class.

Solution 3

If the simple console client application is implemented, then why use another thread to connect and receive the data from server?

static void Main(string[] args)
{
    var client = new TcpClient("...", ...);
    var buffer = new byte[1024];
    using (var networkStream = client.GetStream())
    {
        int bytesRead;
        while ((bytesRead = networkStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            var hexString = BitConverter.ToString(buffer, 0, bytesRead);
            Console.WriteLine("Bytes received: {0}", hexString);
        }
    }
}
Share:
16,460
Quv
Author by

Quv

Above or below an average engineer mostly interested in Ruby, Go, JavaScript. Occasionally writting in C#, VB or Scala.

Updated on June 30, 2022

Comments

  • Quv
    Quv almost 2 years

    I wrote a class that tries to read byte data sent by a server, and when my app ends, I also want the loop to end, but it appears NetworkStream.Read() just waits if no data is ever available.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Net.Sockets;
    using System.Threading;
    using System.Net;
    using System.Threading.Tasks;
    namespace Stream
    {
        class Program
        {
            private static TcpClient client = new TcpClient("127.0.0.1", 80);
            private static NetworkStream stream = client.GetStream();
    
            static void Main(string[] args)
            {
                var p = new Program();
                p.Run();
            }
    
            void Run()
            {
                ThreadPool.QueueUserWorkItem(x =>
                {
                    while (true)
                    {
                        stream.Read(new byte[64], 0, 64);
                    }
                });
    
                client.Close();
                // insert Console.ReadLine(); if you want to see an exception occur.
            }
        }
    }
    

    With this code, we'd get

    1. System.IO.IOException saying "Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host",
    2. ObjectDisposedException saying "Cannot access a disposed object", or
    3. System.IO.IOException saying "A blocking operation was interrupted by a call to WSACancelBlockingCall".

    So how could I safely end this method?

  • MarcF
    MarcF about 11 years
    Why use Thread.Sleep(TimeSpan.FromMilliseconds(200)) when Thread.Sleep(200) does the same thing?