简体   繁体   中英

Stopping threads in C#

I need to make TcpClient event driven rather than polling for messages all the time, so I thought: I will create a thread that would wait for a message to come and fire an event once it does. Here is a general idea:

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
            }
        }
    }
}

This for obvious reasons doesn't work correctly:

  • while (!stream.DataAvailable) blocks the thread and uses always 25% CPU (on 4-core CPU), even if no data is there.
  • listener.Wait(); will wait for ever since the while loop doesn't pick up that cancel has been called.

My alternative solution would be using async calls within the listenToMessages method:

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
    }
}

This works exactly as I expected:

  • The CPU is not blocked if there are no messages in the queue, but we are still waiting for them
  • Cancelation request is picked up correctly and thread finished as expected

I wanted to go further though. Since listenToMessages now returns a Task itself, I thought there is no need of starting a task that would execute that method. Here is what I did:

private void startMessageListener()
{
    client = server.AcceptTcpClient();

    //start listening to the messages
    cSource = new CancellationTokenSource();
    listener = listenToMessages(cSource.Token);
}

This doesn't work as I have expected in the sence that when Cancel() is called, the ReadAsync() method doesn't seem to pick up the cancelation message from the token, and the thread doesn't stop, instead it is stuck on the ReadAsync() line.

Any idea why is this happening? I would think the ReadAsync will still pick up the token, as it did before...
Thanks for all your time and help.

-- EDIT --
Ok so after more in depth evaluation my solution no.2 doesn't really work as expected:
the thread itself ends to the caller and so the caller can continue. However, the thread is not "dead", so if we send some data it will execute once more!
Here is an example:

//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();

This will output the message "Got Data" even though in theory we stopped! I will investigate further and report on my findings.

With modern libraries, any time you type new Thread , you've already got legacy code.

The core solution for your situation is asynchronous socket methods. There are a few ways to approach your API design, though: Rx, TPL Dataflow, and plain TAP come to mind. If you truly want events then EAP is an option.

I have a library of EAP sockets here . It does require a synchronizing context, so you'd have to use something like ActionDispatcher (included in the same library) if you need to use it from a Console application (you don't need this if you're using it from WinForms/WPF).

ReadAsync doesn't seem to support cancellation on a NetworkStream - have a look at the answers in this thread:

NetworkStream.ReadAsync with a cancellation token never cancels

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM