【问题标题】:How to End NetworkStream.Read() safely ? (in C# or VB.net)如何安全地结束 NetworkStream.Read() ? (在 C# 或 VB.net 中)
【发布时间】:2026-01-19 07:35:01
【问题描述】:

我编写了一个尝试读取服务器发送的字节数据的类,当我的应用程序结束时,我也希望循环结束,但它似乎 NetworkStream.Read() 只是在没有可用数据时等待。

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.
        }
    }
}

使用这段代码,我们会得到

  1. System.IO.IOException 说“无法从传输连接中读取数据:现有连接被远程主机强行关闭”,
  2. ObjectDisposedException 说“无法访问已处置的对象”,或者
  3. System.IO.IOException 说“阻塞操作被对 WSACancelBlockingCall 的调用中断”。

那么我怎样才能安全地结束这个方法呢?

【问题讨论】:

    标签: c# sockets tcpclient networkstream


    【解决方案1】:

    从我在运行您的程序时看到的情况来看,您的第一个异常“无法从传输连接中读取数据...”不是由 Stream.Read 引发的,而是由 TcpClient 的构造函数引发的,很可能是由于以下事实127.0.0.1:80 上没有服务器接受连接。

    现在,如果您想在 Stream.Read 上完美结束,我会异步执行。通过这种方式,您可以捕获读取期间抛出的任何异常并相应地清理您的程序。这是您的程序的修改版本:

    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
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:
      1. 为避免阻塞,请仅在 DataAvailable 为 true 时调用 Read()。
      2. ThreadPool 不适合长时间运行的任务,因此请重构您的 while 循环
      3. 在读取之前关闭连接(因为工作项是异步执行的)。

      这可能会有所帮助

           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);
           }
      

      你需要在你的真实课堂上自己管理 Finished & Disposed。

      【讨论】:

      • 为什么在 Thread.Sleep(200) 做同样的事情时使用 Thread.Sleep(TimeSpan.FromMilliseconds(200))?
      【解决方案3】:

      如果实现了简单的控制台客户端应用程序,那么为什么要使用另一个线程来连接和接收来自服务器的数据?

      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);
              }
          }
      }
      

      【讨论】: