2013-11-22 135 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的信息:) –