在C#中停止线程

时间:2013-11-22 12:43:07

标签: c# multithreading asynchronous tcp async-await

我需要让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
    }
}

这完全符合我的预期:

  • 如果队列中没有消息,则不会阻止CPU,但我们仍在等待它们
  • 正确选取取消请求,并按预期完成线程

我想走得更远。由于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”,即使理论上我们停止了!我会进一步调查并报告我的发现。

2 个答案:

答案 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