【发布时间】:2013-12-07 08:30:04
【问题描述】:
我需要让 TcpClient 事件驱动而不是一直轮询消息,所以我想:我将创建一个线程来等待消息到来并在它发生后触发事件。这是一个总体思路:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace ThreadsTesting
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
//imitate a remote client connecting
TcpClient remoteClient = new TcpClient();
remoteClient.Connect(IPAddress.Parse("127.0.0.1"), 80);
//start listening to messages
p.startMessageListener();
//send some fake messages from the remote client to our server
for (int i = 0; i < 5; i++)
{
remoteClient.GetStream().Write(new byte[] { 0x80 }, 0, 1);
Thread.Sleep(200);
}
//sleep for a while to make sure the cpu is not used
Console.WriteLine("Sleeping for 2sec");
Thread.Sleep(2000);
//attempt to stop the server
p.stopMessageListener();
Console.ReadKey();
}
private CancellationTokenSource cSource;
private Task listener;
private TcpListener server;
private TcpClient client;
public Program()
{
server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
server.Start();
}
private void startMessageListener()
{
client = server.AcceptTcpClient();
//start listening to the messages
cSource = new CancellationTokenSource();
listener = Task.Factory.StartNew(() => listenToMessages(cSource.Token), cSource.Token);
}
private void stopMessageListener()
{
Console.Out.WriteLine("Close requested");
//send cancelation signal and wait for the thread to finish
cSource.Cancel();
listener.Wait();
Console.WriteLine("Closed");
}
private void listenToMessages(CancellationToken token)
{
NetworkStream stream = client.GetStream();
//check if cancelation requested
while (!token.IsCancellationRequested)
{
//wait for the data to arrive
while (!stream.DataAvailable)
{ }
//read the data (always 1 byte - the message will always be 1 byte)
byte[] bytes = new byte[1];
stream.Read(bytes, 0, 1);
Console.WriteLine("Got Data");
//fire the event
}
}
}
}
这显然无法正常工作:
-
while (!stream.DataAvailable)阻塞线程并始终使用 25% 的 CPU(在 4 核 CPU 上),即使那里没有数据。 -
listener.Wait();将一直等待,因为 while 循环没有发现取消已被调用。
我的替代解决方案是在 listenToMessages 方法中使用异步调用:
private async Task listenToMessages(CancellationToken token)
{
NetworkStream stream = client.GetStream();
//check if cancelation requested
while (!token.IsCancellationRequested)
{
//read the data
byte[] bytes = new byte[1];
await stream.ReadAsync(bytes, 0, 1, token);
Console.WriteLine("Got Data");
//fire the event
}
}
这完全符合我的预期:
- 如果队列中没有消息,CPU 不会阻塞,但我们仍在等待它们
- 取消请求被正确提取,线程按预期完成
我想更进一步。由于 listenToMessages 现在返回一个 Task 本身,我认为没有必要启动一个将执行该方法的任务。这是我所做的:
private void startMessageListener()
{
client = server.AcceptTcpClient();
//start listening to the messages
cSource = new CancellationTokenSource();
listener = listenToMessages(cSource.Token);
}
这不起作用,因为当调用 Cancel() 时,ReadAsync() 方法似乎没有从令牌中获取取消消息,并且线程没有停止,相反,它卡在 ReadAsync() 行上。
知道为什么会这样吗?我认为 ReadAsync 仍会像以前一样拾取令牌...
感谢您的所有时间和帮助。
-- 编辑--
好的,经过更深入的评估,我的解决方案 2 并没有按预期工作:
线程本身结束于调用者,因此调用者可以继续。但是,线程并没有“死”,所以如果我们发送一些数据,它会再次执行!
这是一个例子:
//send some fake messages from the remote client to our server
for (int i = 0; i < 5; i++)
{
remoteClient.GetStream().Write(new byte[] { 0x80 }, 0, 1);
Thread.Sleep(200);
}
Console.WriteLine("Sleeping for 2sec");
Thread.Sleep(2000);
//attempt to stop the server
p.stopListeners();
//check what will happen if we try to write now
remoteClient.GetStream().Write(new byte[] { 0x80 }, 0, 1);
Thread.Sleep(200);
Console.ReadKey();
即使理论上我们停止了,这也会输出消息“Got Data”!我将进一步调查并报告我的发现。
【问题讨论】:
-
好吧,
ReadAsync确实返回了Task<int>,Result是读取的字节数。也许如果您检查返回值并看到它是 0,您可以假设操作已被取消。
标签: c# multithreading asynchronous tcp async-await