读取时TcpClient和StreamReader阻塞

时间:2009-12-31 07:24:56

标签: multithreading client-server tcpclient networkstream

这是我的情况:

我正在编写聊天客户端以连接聊天服务器。我使用TcpClient创建连接并从中获取NetworkStream对象。我使用StreamReader和StreamWriter来回读写数据。

以下是我的阅读内容:

public string Read()
{
 StringBuilder sb = new StringBuilder();
 try
 {
  int tmp;
  while (true)
  {                
   tmp = StreamReader.Read();
   if (tmp == 0)
    break;
   else
    sb.Append((char)tmp);
   Thread.Sleep(1);
  }
 }
 catch (Exception ex)
 {
  // log exception
 }
 return sb.ToString();
}

这很好,花花公子。在我的主程序中,我创建了一个不断调用此Read方法的线程,以查看是否有数据。下面是一个例子。

private void Listen()
{
 try
 {
  while (IsShuttingDown == false)
  {
   string data = Read();
   if (!string.IsNullOrEmpty(data))
   {
    // do stuff
   }
  }
 }
 catch (ThreadInterruptedException ex)
 {
  // log it
 }
}

...

Thread listenThread = new Thread(new ThreadStart(Listen));
listenThread.Start();

这很好用。当我想关闭应用程序时出现问题。我从UI收到一个关闭命令,并告诉监听线程停止监听(即停止调用此读取函数)。我调用Join并等待这个子线程停止运行。像这样:

// tell the thread to stop listening and wait for a sec
IsShuttingDown = true;            
Thread.Sleep(TimeSpan.FromSeconds(1.00));

// if we've reach here and the thread is still alive
// interrupt it and tell it to quit
if (listenThread.IsAlive)
    listenThread.Interrupt();

// wait until thread is done
listenThread.Join();

问题是它永远不会停止运行!我进入代码并且监听线程阻塞,因为Read()方法是阻塞的。读()只是坐在那里,不会返回。因此,线程永远不会有机会睡眠1毫秒然后被打断。

我确定如果我让它足够长,我会得到另一个数据包并有机会让线程休眠(如果它是一个活跃的聊天室或从服务器获取ping)。但我不想依赖于此。如果用户说关闭我想关闭它!!

我发现的一个替代方法是使用NetworkStream的DataAvailable方法,以便在调用StreamReader.Read()之前检查它。这不起作用,因为它是不可靠的,我从服务器读取数据包时丢失了数据。 (因为我无法正确登录等,等等)

有关如何正常关闭此线程的任何想法?我不想在监听线程上调用Abort()...

2 个答案:

答案 0 :(得分:2)

真正唯一的答案是停止使用Read并切换到使用异步操作(即BeginRead)。这是一个更难处理的模型,但意味着没有线程被阻止(即使客户端没有发送任何数据,也不需要为每个客户端专门设置一个线程 - 一个非常昂贵的资源)。

顺便说一句,在并发代码中使用Thread.Sleep是一种难闻的气味(在重构意义上),它通常表示更深层次的问题(在这种情况下,应该进行异步,非阻塞,操作)。 / p>

答案 1 :(得分:1)

您实际上是使用System.IO.StreamReaderSystem.IO.StreamWriter从套接字发送和接收数据吗?我不知道这是可能的。我只使用了Read() Write()方法返回的NetworkStream对象上的TcpClientGetStream()方法。

假设这是可能的,StreamReader在到达流的末尾时返回-1,而不是0.因此我认为你的Read()方法处于无限循环中。