Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TcpListener: how to stop listening while awaiting AcceptTcpClientAsync()?

Tags:

I don't know how to properly close a TcpListener while an async method await for incoming connections. I found this code on SO, here the code :

public class Server
{
    private TcpListener _Server;
    private bool _Active;

    public Server()
    {
        _Server = new TcpListener(IPAddress.Any, 5555);
    }

    public async void StartListening()
    {
        _Active = true;
        _Server.Start();
        await AcceptConnections();
    }

    public void StopListening()
    {
        _Active = false;
        _Server.Stop();
    }

    private async Task AcceptConnections()
    {
        while (_Active)
        {
            var client = await _Server.AcceptTcpClientAsync();
            DoStuffWithClient(client);
        }
    }

    private void DoStuffWithClient(TcpClient client)
    {
        // ...
    }

}

And the Main :

    static void Main(string[] args)
    {
        var server = new Server();
        server.StartListening();

        Thread.Sleep(5000);

        server.StopListening();
        Console.Read();
    }

An exception is throwed on this line

        await AcceptConnections();

when I call Server.StopListening(), the object is deleted.

So my question is, how can I cancel AcceptTcpClientAsync() for closing TcpListener properly.

like image 779
Bastiflew Avatar asked Oct 07 '13 09:10

Bastiflew


People also ask

How do I stop TCP listening?

You can stop Wait on Listener by killing the listener using TCP Close. You'll want to replace TCP Listen with TCP Create Listener and TCP Wait on Listener so you can fork the listener wire prior to starting to wait on it.

How do you stop a TCP port from listening in C#?

Probably best to use the asynchronous BeginAcceptTcpClient function. Then you can just call Stop() on the listener as it won't be blocking.


2 Answers

Since there's no proper working example here, here is one:

Assuming you have in scope both cancellationToken and tcpListener, then you can do the following:

using (cancellationToken.Register(() => tcpListener.Stop()))
{
    try
    {
        var tcpClient = await tcpListener.AcceptTcpClientAsync();
        // … carry on …
    }
    catch (InvalidOperationException)
    {
        // Either tcpListener.Start wasn't called (a bug!)
        // or the CancellationToken was cancelled before
        // we started accepting (giving an InvalidOperationException),
        // or the CancellationToken was cancelled after
        // we started accepting (giving an ObjectDisposedException).
        //
        // In the latter two cases we should surface the cancellation
        // exception, or otherwise rethrow the original exception.
        cancellationToken.ThrowIfCancellationRequested();
        throw;
    }
}
like image 170
porges Avatar answered Sep 23 '22 04:09

porges


While there is a fairly complicated solution based on a blog post by Stephen Toub, there's much simpler solution using builtin .NET APIs:

var cancellation = new CancellationTokenSource();
await Task.Run(() => listener.AcceptTcpClientAsync(), cancellation.Token);

// somewhere in another thread
cancellation.Cancel();

This solution won't kill the pending accept call. But the other solutions don't do that either and this solution is at least shorter.

Update: A more complete example that shows what should happen after the cancellation is signaled:

var cancellation = new CancellationTokenSource();
var listener = new TcpListener(IPAddress.Any, 5555);
listener.Start();
try
{
    while (true)
    {
        var client = await Task.Run(
            () => listener.AcceptTcpClientAsync(),
            cancellation.Token);
        // use the client, pass CancellationToken to other blocking methods too
    }
}
finally
{
    listener.Stop();
}

// somewhere in another thread
cancellation.Cancel();

Update 2: Task.Run only checks the cancellation token when the task starts. To speed up termination of the accept loop, you might wish to register cancellation action:

cancellation.Token.Register(() => listener.Stop());
like image 39
Robert Važan Avatar answered Sep 19 '22 04:09

Robert Važan