4

I have two buttons that start and stop a TcpListener.

private void buttonStartServer_Click(object sender, EventArgs e)
{
    ThreadPool.SetMinThreads(50, 50);
    IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
    _listener = new TcpListener(ipAddress, 5000);
    cancelSource = new CancellationTokenSource();
    CancellationToken token = cancelSource.Token;
    var taskListener = Task.Factory.StartNew(
                        (t) => Listener(token),token,TaskCreationOptions.LongRunning);
}

void Listener(CancellationToken token)
{
    _listener.Start();
    while (!token.IsCancellationRequested)
    {
        TcpClient c;
        try
        {
            c = _listener.AcceptTcpClient();
        }
        catch
        {
            break;
        }
        Task t = Task.Factory.StartNew(() => Accept(c))
            .ContinueWith(ant => richTextBoxMessage.AppendText(ant.Result), _uiScheduler);
    }
}

private void buttonStopServer_Click(object sender, EventArgs e)
{
    cancelSource.Cancel();
    _listener.Stop();
    richTextBoxMessage.AppendText("Server shutdown");
}

Accept is some method that reads from the TcpClient. My question is, before I stop the server by clicking the button, my server is blocked at

try {c = _listener.AcceptTcpClient();}

So how does clicking the cancel button kill the taskListener? Without having a ManualResetEvent or ManualResetEventSlim? I am able to toggle between server shutdown and server restart. What's going on under the hood? I'm targeting .NET 4.0

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
user2635088
  • 1,598
  • 1
  • 24
  • 43

1 Answers1

3

So how does clicking the cancel button kill the taskListener?

When you call TcpListener.Stop in your cancel event handler, internally it will close the underlying Socket, and raise a SocketException. This exception is swallowed by your catch all block, which simply breaks the loop.

The documentation state this explicitly (emphasis mine):

Stop closes the listener. Any unaccepted connection requests in the queue will be lost. Remote hosts waiting for a connection to be accepted will throw a SocketException. You are responsible for closing your accepted connections separately.

You can see this by printing out the exception in the catch block:

TcpClient c;
try
{
    c = _listener.AcceptTcpClient();
}
catch (SocketException e)
{
    Debug.WriteLine("Socket exception was raised: {0}", e);
    if (e.SocketErrorCode == SocketError.Interrupted)
        Debug.WriteLine("Blocking listen was interrupted");
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Gotcha - so the cancellation token isn't really doing anything? What's a better practice? Killing it this way or using the cancellation token? – user2635088 Dec 11 '15 at 10:37
  • The problem is there is no overload of `TcpListener.AcceptTcpClient` that takes a cancellation token, so you're forced to call `Stop` and catch the exception. If you can use the [`Microsoft.Bcl.Async`](https://www.nuget.org/packages/Microsoft.Bcl.Async/) package for .NET 4.0, I'd go with [this approach](http://stackoverflow.com/a/30856169/1870803). – Yuval Itzchakov Dec 11 '15 at 10:43