我需要使TcpClient事件驱动,而不是始终轮询消息,所以我想:我会创建一个线程,等待消息到来并在事件发生后触发事件。这是一个总体思路:在C#中停止线程
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);
}
正如我预料的SENCE,当取消(这不起作用)被调用时,ReadAsync()方法似乎并没有拿起从该取消的消息令牌,并且线程不停止,而是卡在ReadAsync()行上。
任何想法为什么会发生这种情况?我会认为ReadAsync仍然会拿起令牌,像以前一样...
感谢您的所有时间和帮助。
- 编辑 -
好了,所以以后更深入的评价我的解决方案二号并没有真正按预期工作:
线程本身终止给调用者,因此主叫方可以继续。但是,线程并不是“死”的,所以如果我们发送一些数据,它会再次执行!
下面是一个例子:
//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();
这将输出消息“得到数据”,即使在理论上,我们停了!我会进一步调查并报告我的发现。
好吧,'ReadAsync'确实会返回一个'Task',而'Result'是读取的字节数。也许如果你检查返回值并且看到它是0,那么你可以假定操作被取消了。 –