A TcpListener opens a socket using TCP to listen for incoming connection requests. You can use AcceptTcpClientAsync to asynchronously get a TcpClient object, which you can then use to send and receive messages on the connection.
AcceptTcpClientAsync
Waits forever to get a connection. This is pretty much what you want most of the time… after all, servers don’t have much to do until they have a client!
However, I wanted a clean way to shut down the server. In my case, the server is part of a testing harness, so it needs to be torn down and built up on a regular basis. Sockets are managed by the operating system, so you want to clean them up properly – especially if you’re just going to try and re-use them in a short period of time. In other words, they implement IDisposable and you should be conscious of that.
CancellationToken
Normally, you would be able to cancel an asynchronous Task using a CancellationToken, which I talked all about in my last blog post.
However, AcceptTcpClientAsync does not include an overload that accepts a CancellationToken…. so it will just run until you get a TcpClient connected. Luckily, there’s a way that we can still leverage the CancellationToken and maintain the async/await paradigm throughout the application.
Register
You can Register an Action to execute when a CancellationToken receives its Cancel notification. This allows you to hook in to that Cancel event even without the method directly supporting CancellationTokens.
NOTE – The Register method creates a CancellationTokenRegistration object, which implements IDisposable, so you need to make sure to Dispose of it properly!
Use with TcpListener
To use with a TcpListener, we follow the same procedure as above, but we call listener.Stop inside of the registration action. When this happens, the in-progress AcceptTcpClientAsync *will* throw an ObjectDisposedException, so we need to catch that. For good measure, you could check to make sure that the CancellationToken is actually canceled before ignoring the exception.
If you want to keep your TcpListener viable to start accepting TCP connections again, you also need to call listener.Start() to re-bind the socket. If you don’t call Start(), you will get an InvalidOperationException indicating that the listener has not had Start() called.
Extension Method
This can easily be turned in to an extension method for TcpListener simply by making the method static and adding this in front of the first parameter:
Robert N.
Hi Robert,
thank you for the insight – saved my life. It works, but I don’t understand one thing:
Why do you call “ConfigureAwait(false)”? I figured if I don’t call that, the ObjectDisposedException will not be caught.
Would appreciate it if you had the time to clarify this.
Thank you,
Robert.
Robert
Hey Robert,
You don’t need to use ConfigureAwait(false), it will behave the same either way.
The ConfigureAwait(false) makes it so that the Context isn’t saved for when the thread returns. In the case of a MVC controller, for example, where you have an HttpContext, you wouldn’t want to put ConfigureAwait(false) because you want that HttpContext to still be the same one when the awaited call returns. In other scenarios (like this one) where there is no context, putting ConfigureAwait(false) saves a CPU cycle or two. Stephen Cleary goes in to it in a lot more detail on his blog.
Hope this helps!
Robert N.
Thank you very much for the fast reply!
I am still trying to wrap my head around that blog
post.
My problem is that the behaviour is not the same here if I omit the ConfigureAwait(false).
This is the call:
var tcpClient = await m_TcpListener
.AcceptTcpClientAsync()
.ConfigureAwait(false);
It is wrapped in a “try-catch(ObjectDisposedException)” handler.
Without the ConfigureAwaits(false) the exception is never caught, allthough it is still raised.
With and without ConfigureAwaits(false) the debugger tells me that the exception is raised somewhere in “System.Net.Sockets.dll” and my program is not even on the call stack. After continuing the breakpoint in the try-catch-handler is hit with that same exception, but only if ConfigureAwaits(false) was used.
Possibly the exception is not handed over to the thread on which await was used? I should probably not use “await” without understanding what is going on here.
Best,
Robert.
Robert
Hey Robert,
The ObjectDisposedException does indeed originate from System.Net.Sockets, so that seems right to me.
I wrote this little app that cancels when you press “escape”, and its all working as expected for me with and without the ConfigureAwait(false)… Anything look different compared to your code?
Robert N.
Excellent, this is working for me and it proved it had to be something specific about my program. So I started digging:
My code is essentially the same as your but its a WPF app (.NET Core 3.1).
I created a console app (.NET Core 3.1) and moved the listening stuff there -> the problem was gone.
I created a WPF app (.NET Core 3.1) and copied the listening stuff there -> the problem was gone.
Comparing to my main app there was only one difference left and it proved to be the cause of all this:
– My test WPF app cancelled from the OnClosing event handler of the main window.
– My main app cancelled from the OnExit overload of the App class
If you cancel the token from the OnExit overload of the Application class the catch handle will only work if the await was configured with ConfigureAwait(false).
Here is the App class (main window implementation does nothing). If you place a breakpoint in the catch handler and alternate between running this with/without ConfigureAwaits, the described behaviour should be observable:
public partial class App : Application
{
#region Private Fields
TcpListener m_TcpListener;
bool m_Listening;
CancellationTokenSource m_TcpListenerAcceptTokenSource;
#endregion
public void Stop()
{
m_Listening = false;
m_TcpListenerAcceptTokenSource.Cancel();
}
#region Protected Overriding Application
protected override void OnStartup(StartupEventArgs e)
{
m_TcpListener = new TcpListener(IPAddress.Any, 12366);
// Must be set before calling start
m_Listening = true;
m_TcpListener.Start();
AcceptClients();
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
Stop();
base.OnExit(e);
}
#endregion
#region Private Methods
async void AcceptClients()
{
while (m_Listening)
{
if (m_TcpListenerAcceptTokenSource == null)
m_TcpListenerAcceptTokenSource = new CancellationTokenSource();
using (m_TcpListenerAcceptTokenSource.Token.Register(m_TcpListener.Stop))
{
try
{
// The ConfigureAwait must be called if we want to be
// able to catch the ObjectDisposedException caused by
// calling Stop() while a AcceptTcpClientAsync() is pending
// Many details on ConfigureAwait:
// https://devblogs.microsoft.com/dotnet/configureawait-faq/
var tcpClient = await m_TcpListener
.AcceptTcpClientAsync()
.ConfigureAwait(false);
}
catch (ObjectDisposedException ex) when (m_TcpListenerAcceptTokenSource.IsCancellationRequested)
{
m_TcpListenerAcceptTokenSource = null;
}
}
}
}
#endregion
}
Robert N.
PS: The blog post I linked in the comment inside the source code above did not help me at that point, it rather convinced me that I don’t really understand what is going. Before you helped me find the cause of the problem I already implemented a solution without async/await and with a Thread instance instead, because I try to never use code I don’t understand. Its very cool we found the cause! Still don’t know the exact reason – something special about the program state when the app is being shutdown?
Robert
Glad it is working for you now, but it does seem kind of strange… the UI thread on a WPF app (like the one that would execute OnStartup) has a Context object that you would want to maintain if you’re doing a longer-running call. The default configuration of ConfigureAwait(true) would make it so that this context is preserved, allowing you to have access to the Context object again once control is yielded back to the thread that called the await. However, looking at your code, it doesn’t seem like you would probably need access to the Context, especially since it is when the app is closing.
I’m wondering if the AcceptClients(); is the problem. You can see some weird stuff with async void functions, and I think its generally advised to avoid them if possible. You could try launching it as a separate thread via Task.Run and changing it to async Task… something like:
Task.Run(AcceptClients);
private async Task AcceptClients() { … }
In that way, you might not need to use the ConfigureAwait(false).