我需要使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,那麼你可以假定操作被取消了。 –