我需要让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
}
}
这完全符合我的预期:
我想走得更远。由于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”,即使理论上我们停止了!我会进一步调查并报告我的发现。
答案 0 :(得分:4)
使用现代库,只要您键入new Thread
,就已经有了遗留代码。
适用于您的情况的核心解决方案是异步套接字方法。但是,有几种方法可以实现您的API设计:Rx,TPL Dataflow和简单的TAP。如果您真的想要事件,那么EAP是一个选项。
我有一个EAP套接字库here。它确实需要同步上下文,因此如果您需要从控制台应用程序中使用它,则必须使用类似ActionDispatcher
(包含在同一个库中)的内容(如果您使用它,则不需要它)它来自WinForms / WPF)。
答案 1 :(得分:0)
ReadAsync似乎不支持在NetworkStream上取消 - 请查看此主题中的答案:
NetworkStream.ReadAsync with a cancellation token never cancels