Consider the following simplified example (ready to roll in LinqPad, elevated account required):
void Main()
{
Go();
Thread.Sleep(100000);
}
async void Go()
{
TcpListener listener = new TcpListener(IPAddress.Any, 6666);
try
{
cts.Token.Register(() => Console.WriteLine("Token was canceled"));
listener.Start();
using(TcpClient client = await listener.AcceptTcpClientAsync()
.ConfigureAwait(false))
using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
var stream=client.GetStream();
var buffer=new byte[64];
try
{
var amtRead = await stream.ReadAsync(buffer,
0,
buffer.Length,
cts.Token);
Console.WriteLine("finished");
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
finally
{
listener.Stop();
}
}
If I connect a telnet client to localhost:6666 and sit around doing nothing for 5 seconds, why do I see "Token was canceled" but never see "boom" (or "finished")?
Will this NetworkStream not respect cancellation?
I can work around this with a combination of Task.Delay() and Task.WhenAny, but I'd prefer to get it working as expected.
Conversely, the following example of cancellation:
async void Go(CancellationToken ct)
{
using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
try
{
await Task.Delay(TimeSpan.FromSeconds(10),cts.Token)
.ConfigureAwait(false);
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
Prints "boom", as expected. What's going on?
No, NetworkStream does not support cancellation.
Unfortunately, the underlying Win32 APIs do not always support per-operation cancellation. Traditionally, you could cancel all I/O for a particular handle, but the method to cancel a single I/O operation is fairly recent. Most of the .NET BCL was written against the XP API (or older), which did not include CancelIoEx.
Stream compounds this issue by "faking" support for cancellation (and asynchronous I/O, too) even if the implementation doesn't support it. The "fake" support for cancellation just checks the token immediately and then starts a regular asynchronous read that cannot be cancelled. That's what you're seeing happen with NetworkStream.
With sockets (and most Win32 types), the traditional approach is to close the handle if you want to abort communications. This causes all current operations (both reads and writes) to fail. Technically this is a violation of BCL thread safety as documented, but it does work.
cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)
If, on the other hand, you want to detect a half-open scenario (where your side is reading but the other side has lost its connection), then the best solution is to periodically send data. I describe this more on my blog.
Related
So I have a TcpClient in a console app that is listening on port 9096. I want the client to be able to handle multiple connections (simultaneous or not). I also do not want to use Threads. I want to use async/await. I also need to be able to gracefully close the app during certain events, being careful not to lose any data. So I need a cancellation token. I have the code mostly working but there are two issues.
First, when the app starts listening and I send it data; everything works correctly as long as the sender is using the same initial connection to the app. Once a new connection (or socket I guess? not clear on the terminology) is established the app does not process the new data.
Second, when the terminate signal is given to the app and the token is canceled the app does not close. I am not getting any exceptions and I cannot figure out what I an doing wrong.
I have looked all over and cannot find an example of a TcpClient that uses async/await with a cancellation token. I also cannot find an example that I have been able to get working that correctly processes multiple connections, without using Threads or other complicated designs. I want the design as simple as possible with as little code as possible while still meeting my requirements. If using threads is the only way to do it I will, but I am soo close to getting it right I feel like I am just missing a little thing.
I am trying to figure this out at my wits end and have exhausted all my ideas.
EDIT: I moved the AcceptTcpClientAsync into the loop as suggested below and it did not change anything. The app functions the same as before.
Program.cs
class Program
{
private static List<Task> _listeners = new List<Task>();
private static readonly CancellationTokenSource cancelSource = new CancellationTokenSource();
static void Main(string[] args)
{
Console.TreatControlCAsInput = false;
Console.CancelKeyPress += (o, e) => {
Console.WriteLine("Shutting down.");
cancelSource.Cancel();
};
Console.WriteLine("Started, press ctrl + c to terminate.");
_listeners.Add(Listen(cancelSource.Token));
cancelSource.Token.WaitHandle.WaitOne();
Task.WaitAll(_listeners.ToArray(), cancelSource.Token);
}
}
Listen
public async Task Listen(CancellationToken token){
var listener = new TcpListener(IPAddress.Parse("0.0.0.0"), 9096);
listener.Start();
Console.WriteLine("Listening on port 9096");
while (!token.IsCancellationRequested) {
// Also tried putting AcceptTcpClientAsync here.
await Task.Run(async () => {
var client = await listener.AcceptTcpClientAsync();
using (var stream = client.GetStream())
using (var streamReader = new StreamReader(stream, Encoding.UTF8))
using (var streamWriter = new StreamWriter(stream, Encoding.UTF8)) {
while (!token.IsCancellationRequested) {
// DO WORK WITH DATA RECEIVED
vat data = await streamReader.ReadAsync();
await streamWriter.WriteLineAsync("Request received.");
}
}
});
}
Console.WriteLine("Stopped Accepting Requests.");
listener.Server.Close();
listener.Stop();
}
This is actually working the way you designed it, however you have only built to receive one connection. I am not going to write a full socket implementation for you (as this can get fairly in-depth). However, as for your main problem, you need to put the AcceptTcpClientAsync in the loop otherwise you won't get any more connections:
var cancellation = new CancellationTokenSource();
...
var listener = new TcpListener(...);
listener.Start();
try
{
while (!token.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync()
...
}
}
finally
{
listener.Stop();
}
// somewhere in another thread
cancellation.Cancel();
Update
I tried that and no behavior changed. Still does not pick up any
connection after the first.
await ...
while (!token.IsCancellationRequested) {
// DO WORK WITH DATA RECEIVED
It's obvious that AcceptTcpClientAsync will never get called again because you are awaiting the task. This method is what accepts the client, if you can't call it, you don't get any more clients.
You cannot block here, which is what you are doing. Please see some socket server examples to get a better idea of how to write a listener.
I have written a high performance TCP server in C# using SocketAsyncEventArgs. I have been testing its performance with two very simple clients, each creating 2000 parallel continuous loops. One client makes use of asynchronous calls to TcpClient; the other makes use of synchronous calls.
Asynchronous
Parallel.For(0, numClients, parallelOptions, async i =>
{
while (true)
{
var tcpClient = new TcpClient();
try
{
await tcpClient.ConnectAsync(host, port);
await tcpClient.GetStream().WriteAsync(message);
var buffer = new byte[1024];
await tcpClient.GetStream().ReadAsync(buffer, 0, 1024);
tcpClient.GetStream().Close();
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {ex.Message}");
}
finally
{
tcpClient.Close();
tcpClient.Dispose();
}
}
});
Synchronous
Parallel.For(0, numClients, parallelOptions, i =>
{
while (true)
{
var tcpClient = new TcpClient();
try
{
tcpClient.Connect(host, port);
tcpClient.GetStream().Write(message);
var buffer = new byte[1024];
tcpClient.GetStream().Read(buffer, 0, 1024);
tcpClient.GetStream().Close();
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {ex.Message}");
}
finally
{
tcpClient.Close();
tcpClient.Dispose();
}
}
});
The synchronous version iterates continuously without any errors.
The asynchronous version, however, results in many No connection could be made because the target machine actively refused it errors. My assumptions are that this client is flooding the TCP listen backlog queue, causing subsequent inbound connections to be rejected.
What's going on? How can I protect server throughput from clients that choose to connect asynchronously?
Parallel.For is designed to wait for completion of all tasks inside the loop. But in case of asynchronous calls the loop delegate completes immediately returning a promise for internal asynchronous call. However "nobody" outside does wait for this promise completion because Parallel.For isn't designed for that which leads to the situation when Parallel.For quickly fires the tasks which are able only to start very first asynchronous operation (which is ConnectAsync) and that in turns most likely leads to the SYN flooding. But there can be other side effects caused by the fact the asynchronous operations aren't awaited.
I'm getting a bit confused with Task.Run and all I read about it on the internet. So here's my case: I have some function that handles incoming socket data:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
await this.ProcessData(client, data);
}
}
but this has a disadvantage that I can only read next request once I've finished processing the last one. So here's a modified version:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
Task.Run(async () => {
await this.ProcessData(client, data);
});
}
}
It's a simplified version. For more advanced one I would restrict the maximum amount of parallel requests of course.
Anyway this ProcessData is mostly IO-bound (doing some calls to dbs, very light processing and sending data back to client) yet I keep reading that I should use Task.Run with CPU-bound functions.
Is that a correct usage of Task.Run for my case? If not what would be an alternative?
Conceptually, that is a fine usage of Task.Run. It's very similar to how ASP.NET dispatches requests: (asynchronously) reading a request and then dispatching (synchronous or asynchronous) work to the thread pool.
In practice, you'll want to ensure that the result of ProcessData is handled properly. In particular, you'll want to observe exceptions. As the code currently stands, any exceptions from ProcessData will be swallowed, since the task returned from Task.Run is not observed.
IMO, the cleanest way to handle per-message errors is to have your own try/catch, as such:
public async Task Handle(Client client)
{
while (true)
{
var data = await client.ReadAsync();
Task.Run(async () => {
try { await this.ProcessData(client, data); }
catch (Exception ex) {
// TODO: handle
}
});
}
}
where the // TODO: handle is the appropriate error-handling code for your application. E.g., you might send an error response on the socket, or just log-and-ignore.
Consider the following simplified example (ready to roll in LinqPad, elevated account required):
void Main()
{
Go();
Thread.Sleep(100000);
}
async void Go()
{
TcpListener listener = new TcpListener(IPAddress.Any, 6666);
try
{
cts.Token.Register(() => Console.WriteLine("Token was canceled"));
listener.Start();
using(TcpClient client = await listener.AcceptTcpClientAsync()
.ConfigureAwait(false))
using(var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
var stream=client.GetStream();
var buffer=new byte[64];
try
{
var amtRead = await stream.ReadAsync(buffer,
0,
buffer.Length,
cts.Token);
Console.WriteLine("finished");
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
finally
{
listener.Stop();
}
}
If I connect a telnet client to localhost:6666 and sit around doing nothing for 5 seconds, why do I see "Token was canceled" but never see "boom" (or "finished")?
Will this NetworkStream not respect cancellation?
I can work around this with a combination of Task.Delay() and Task.WhenAny, but I'd prefer to get it working as expected.
Conversely, the following example of cancellation:
async void Go(CancellationToken ct)
{
using(var cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
try
{
await Task.Delay(TimeSpan.FromSeconds(10),cts.Token)
.ConfigureAwait(false);
}
catch(TaskCanceledException)
{
Console.WriteLine("boom");
}
}
}
Prints "boom", as expected. What's going on?
No, NetworkStream does not support cancellation.
Unfortunately, the underlying Win32 APIs do not always support per-operation cancellation. Traditionally, you could cancel all I/O for a particular handle, but the method to cancel a single I/O operation is fairly recent. Most of the .NET BCL was written against the XP API (or older), which did not include CancelIoEx.
Stream compounds this issue by "faking" support for cancellation (and asynchronous I/O, too) even if the implementation doesn't support it. The "fake" support for cancellation just checks the token immediately and then starts a regular asynchronous read that cannot be cancelled. That's what you're seeing happen with NetworkStream.
With sockets (and most Win32 types), the traditional approach is to close the handle if you want to abort communications. This causes all current operations (both reads and writes) to fail. Technically this is a violation of BCL thread safety as documented, but it does work.
cts.Token.Register(() => client.Close());
...
catch (ObjectDisposedException)
If, on the other hand, you want to detect a half-open scenario (where your side is reading but the other side has lost its connection), then the best solution is to periodically send data. I describe this more on my blog.
I have been thinking about what is the right way of set up a TCP server by using asynchronous programming.
Usually I would spawn a thread per incoming request, but I would like to do the most of the ThreadPool, so when connections are idle, there are no blocked threads.
First I would create the listener and start to accept clients, in this case, in a Console app:
static void Main(string[] args)
{
CancellationTokenSource cancellation = new CancellationTokenSource();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
TcpListener server = new TcpListener(endpoint);
server.Start();
var task = AcceptTcpClients(server, cancellation.Token);
Console.ReadKey(true);
cancellation.Cancel();
await task;
Console.ReadKey(true);
}
In that method, I would loop accepting incoming requests and spawning a new Task to handle the connection, so the loop can get back to accept more clients:
static async Task AcceptTcpClients(TcpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var ws = await server.AcceptTcpClientAsync();
Task.Factory.StartNew(async () =>
{
while (ws.IsConnected && !token.IsCancellationRequested)
{
String msg = await ws.ReadAsync();
if (msg != null)
await ws.WriteAsync(ProcessResponse(msg));
}
}, token);
}
}
Creating new Task does not necessarily mean new thread, but is this the right way? Am I taking advantage of the ThreadPool or is there anything else I can do?
Is there any potential pitfall in this approach?
The await task; in your Main won't compile; you'll have to use task.Wait(); if you want to block on it.
Also, you should use Task.Run instead of Task.Factory.StartNew in asynchronous programming.
Creating new Task does not necessarily mean new thread, but is this the right way?
You certainly can start up separate tasks (using Task.Run). Though you don't have to. You could just as easily call a separate async method to handle the individual socket connections.
There are a few problems with your actual socket handling, though. The Connected property is practically useless. You should always be continuously reading from a connected socket, even while you're writing to it. Also, you should be writing "keepalive" messages or have a timeout on your reads, so that you can detect half-open situations. I maintain a TCP/IP .NET FAQ that explains these common problems.
I really, strongly recommend that people do not write TCP/IP servers or clients. There are tons of pitfalls. It would be far better to self-host WebAPI and/or SignalR, if possible.
In order to stop a server accept loop gracefully, I register a callback that stops listening when the cancellationToken is cancelled (cancellationToken.Register(listener.Stop);).
This will throw a SocketException on await listener.AcceptTcpClientAsync(); that is easy to capture.
There is no need for Task.Run(HandleClient()), because calling an async method returns a task that is running in parallel.
public async Task Run(CancellationToken cancellationToken)
{
TcpListener listener = new TcpListener(address, port);
listener.Start();
cancellationToken.Register(listener.Stop);
while (!cancellationToken.IsCancellationRequested)
{
try
{
TcpClient client = await listener.AcceptTcpClientAsync();
var clientTask = protocol.HandleClient(client, cancellationToken)
.ContinueWith(antecedent => client.Dispose())
.ContinueWith(antecedent => logger.LogInformation("Client disposed."));
}
catch (SocketException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation("TcpListener stopped listening because cancellation was requested.");
}
catch (Exception ex)
{
logger.LogError(new EventId(), ex, $"Error handling client: {ex.Message}");
}
}
}