TcpClient 和 StreamReader 阻止读取
这是我的情况:
我正在编写一个聊天客户端来连接到聊天服务器。我使用 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() 方法被阻塞。 Read() 只是坐在那里并且不返回。因此,线程永远没有机会休眠 1 毫秒然后被中断。
我确信如果我让它停留足够长的时间,我会收到另一个数据包,并有机会让线程休眠(如果它是一个活动的聊天室或从服务器获取 ping)。但我不想依赖于此。如果用户说关闭我想关闭它!
我发现的一种替代方法是使用 DataAvailable 方法,以便我可以在调用 StreamReader.Read() 之前检查它。这不起作用,因为它不可靠,并且我在从服务器读取数据包时丢失了数据。 (因为我无法正确登录,等等)
关于如何优雅地关闭这个线程有什么想法吗?我不想在监听线程上调用 Abort()...
Here's my situation:
I'm writing a chat client to connect to a chat server. I create the connection using a TcpClient and get a NetworkStream object from it. I use a StreamReader and StreamWriter to read and write data back and forth.
Here's what my read looks like:
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();
}
That works fine and dandy. In my main program I create a thread that continually calls this Read method to see if there is data. An example is below.
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();
This works just fine. The problem comes when I want to shut down the application. I receive a shut down command from the UI, and tell the listening thread to stop listening (that is, stop calling this read function). I call Join and wait for this child thread to stop running. Like so:
// 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();
The problem is it never stops running! I stepped into the code and the listening thread is blocking because the Read() method is blocking. Read() just sits there and doesn't return. Hence, the thread never gets a chance to sleep that 1 millisecond and then get interrupted.
I'm sure if I let it sit long enough I'd get another packet and get a chance for the thread to sleep (if it's an active chatroom or a get a ping from the server). But I don't want to depend on that. If the user says shut down I want to shut it down!!
One alternative I found is to use the DataAvailable method of NetworkStream so that I could check it before I called StreamReader.Read(). This didn't work because it was undependable and I lost data when reading from packets from the server. (Because of that I wasn't able to login correctly, etc, etc)
Any ideas on how to shutdown this thread gracefully? I'd hate to call Abort() on the listening thread...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
实际上,唯一的答案是停止使用
Read
并切换到使用异步操作(即BeginRead
)。这是一个更难使用的模型,但意味着没有线程被阻塞(并且您不需要为每个客户端分配一个线程(一种非常昂贵的资源),即使客户端没有发送任何数据)。顺便说一句,在并发代码中使用 Thread.Sleep 是一种不好的味道(在重构意义上),它通常表明更深层次的问题(在这种情况下,应该执行异步、非阻塞操作) 。
Really the only answer is to stop using
Read
and switch to using asynchronous operations (i.e.BeginRead
). This is a harder model to work with, but means no thread is blocked (and you don't need to dedicate a thread—a very expensive resource—to each client even if the client is not sending any data).By the way, using
Thread.Sleep
in concurrent code is a bad smell (in the Refactoring sense), it usually indicates deeper problems (in this case, should be doing asynchronous, non-blocking, operations).您实际上是否使用 System.IO.StreamReader 和 System.IO.StreamWriter 从套接字发送和接收数据?我不知道这是可能的。我只在
TcpClient
NetworkStream 对象上使用过Read()
和Write()
方法> 的GetStream()
方法。假设这是可能的,当到达流末尾时,
StreamReader
返回 -1,而不是 0。所以在我看来,您的Read()
方法处于无限状态环形。Are you actually using
System.IO.StreamReader
andSystem.IO.StreamWriter
to send and receive data from the socket? I wasn't aware this was possible. I've only ever used theRead()
andWrite()
methods on theNetworkStream
object returned by theTcpClient
'sGetStream()
method.Assuming this is possible,
StreamReader
returns -1 when the end of the stream is reached, not 0. So it looks to me like yourRead()
method is in an infinite loop.