Reputation: 14417
Here's the entry point:
public class Program
{
private static readonly CancellationTokenSource TokenSource = new CancellationTokenSource();
public static void Main(string[] args)
{
// start the app
new Bootstrap()
.RunAsync(TokenSource.Token)
.Wait();
Console.CancelKeyPress += (sender, eventArgs) =>
{
TokenSource.CancelAfter(500);
};
}
}
Here's the bootstrap:
public class Bootstrap : IBootstrap
{
private readonly IServer server;
private readonly ILogger logger;
public Bootstrap(
IServer server,
ILogger logger)
{
this.server = server;
this.logger = logger;
}
public async Task RunAsync(CancellationToken token)
{
this.logger.Info("Application Starting...");
await this.server.StartAsync(token);
}
}
Here's the server:
public abstract class BaseServer : IServer
{
private readonly IPAddress ipAddress;
private readonly int port;
private readonly ILogger logger;
protected BaseServer(
string ipAddress,
int port,
ILogger logger)
{
this.ipAddress = IPAddress.Parse(ipAddress);
this.port = port;
this.logger = logger;
}
public async Task StartAsync(CancellationToken token)
{
this.logger.Debug("[{0}] Listening for connections using: {1}:{2}", this.GetType().Name, this.ipAddress.ToString(), this.port);
var tcpListener = new TcpListener(this.ipAddress, this.port);
tcpListener.Start();
while (!token.IsCancellationRequested)
{
await this.ServerProcessorAsync(tcpListener, token);
}
tcpListener.Stop();
Console.WriteLine("Stopped Listener");
}
public abstract Task ServerProcessorAsync(TcpListener listener, CancellationToken token);
}
Here's the Server Processor:
public class Server : BaseServer
{
private readonly ILogger logger;
public Server(
IAppConfiguration configuration,
ILogger logger)
: base(configuration.IpAddress, configuration.Port, logger)
{
this.logger = logger;
}
public override async Task ServerProcessorAsync(TcpListener listener, CancellationToken token)
{
this.logger.Debug("[{0}] Waiting for connection...", this.GetType().Name);
var client = await listener.AcceptTcpClientAsync();
this.logger.Debug("[{0}] Spawning Thread for Connection...", this.GetType().Name);
Parallel.Invoke(new ParallelOptions
{
CancellationToken = token,
MaxDegreeOfParallelism = 10000,
TaskScheduler = TaskScheduler.Current
}, () => this.ListenToClient(client));
}
private void ListenToClient(TcpClient client)
{
var threadName = Thread.CurrentThread.Name;
var bytes = new byte[2048];
var stream = client.GetStream();
int i;
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
var timeString = DateTime.Now.ToLongTimeString();
var currentRes = Encoding.UTF8.GetString(bytes);
var received = $"Recieved [{threadName}] [{timeString}]: {currentRes}";
this.logger.Info(received);
var responseData = Encoding.UTF8.GetBytes(received);
stream.Write(responseData, 0, responseData.Length);
}
client.Close();
}
}
Will this correctly shut the app down when ctrl+c
is pressed?
Is there a way to debug this, or to know that the resources have been released properly.
I assume that the while (!token.IsCancellationRequested)
will break when ctrl+c
. I also assume that that if there are any threads created by Parallel.Invoke
they will disposed of when the Cancel
is called.
If in the case that:
Console.CancelKeyPress += (sender, eventArgs) =>
{
TokenSource.CancelAfter(500);
};
doesn't wait for things to be cleared up, is there a better way than a timeout to make sure that everything is properly cleared up?
Upvotes: 0
Views: 612
Reputation: 101483
First, you Wait on RunAsync before subscribing to Console.CancelKeyPress event, so you will subscribe to it when it's too late.
Second, cancellation token won't work anyway in your case. This line:
var client = await listener.AcceptTcpClientAsync();
Will block until new client connects, and because AcceptTcpClientAsync
does not has overload which accepts CancellationToken
- usage of CancellationTokens
in the whole program becomes not needed. What you should do instead is stop your listener instead of cancellation. This will throw exception on the line above, which you should catch and gracefully end the task.
If you really want to continue with CancellationToken, even if it's not really needed here, consider this approach to make it work with AcceptTcpClientAsync
: https://stackoverflow.com/a/14524565/5311735. This might also be good idea if you use CancellationToken
to cancel many different operations not shown in your question.
Upvotes: 1