2013-11-22 100 views
2

我需要使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(); 

这将输出消息“得到数据”,即使在理论上,我们停了!我会进一步调查并报告我的发现。

+0

好吧,'ReadAsync'确实会返回一个'Task ',而'Result'是读取的字节数。也许如果你检查返回值并且看到它是0,那么你可以假定操作被取消了。 –

回答

0

ReadAsync似乎并不支持在取消的NetworkStream - 看看在这个线程的答案:

NetworkStream.ReadAsync with a cancellation token never cancels

+0

感谢您的链接。在那种情况下,我的第二个解决方案如何正确处理线程?它肯定会卡在同一条线上,对吧? –

+0

标记你的答案,因为它完全回答了我的问题。谢谢。 –

4

随着现代图书馆,键入new Thread任何时候,你已经有了遗留代码。

您的情况的核心解决方案是异步套接字方法。但是,有几种方法可以用来处理您的API设计:Rx,TPL Dataflow和普通的TAP。如果你真的想事件那么EAP是一个选项。

我有一个EAP插座库here。它确实需要一个同步上下文,所以如果你需要从控制台应用程序中使用它,你必须使用类似于ActionDispatcher(包含在同一个库中)的东西(如果你使用WinForms/WPF)。

+0

当我需要一个STA线程时,我仍然使用'new Thread' :) – Noseratio

+1

@Noseratio:好的,好的,但是这是最后一个剩余的用例! :) –

+0

与TPL DataFlow的一个intresting建议。不幸的是,我做出这么大的改变为时已晚。我想我将不得不坚持我的第二个解决方案,因为它的行为如预期。我的主要问题是如何在第二种解决方案中取消ReadAsync,但不是在第三种解决方案中。无论如何,感谢您的帖子。我正在阅读有关EAP的信息:) –