Reputation: 1277
I'm making a simple server that listens for clients which is going to read the clients requests, do some calculations, send a response back to the client and the close again ASAP (somewhat similar to HTTP).
There might be many connections every seconds, so I want it to make it as fast and efficient as possible.
So far, the best way I can think of doing this, is shown as an example below:
private static ManualResetEvent gate = new ManualResetEvent(false);
static async void ListenToClient(TcpListener listener)
{
Console.WriteLine("Waiting for connection");
TcpClient client = await listener.AcceptTcpClientAsync();
Console.WriteLine("Connection accepted & establised");
gate.Set(); //Unblocks the mainthread
Stream stream = client.GetStream();
byte[] requestBuffer = new byte[1024];
int size = await stream.ReadAsync(requestBuffer, 0, requestBuffer.Length);
//PSEUDO CODE: Do some calculations
byte[] responseBuffer = Encoding.ASCII.GetBytes("Ok");
await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length);
stream.Close();
client.Close();
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
ListenToClient(listener);
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
Note: for this example and simplicity, I haven't made any error handling like try-catch and I know the response here will always be "Ok"
The code here is simply waiting for a connection, when it arrives to await listener.AcceptTcpClientAsync()
, then it jumps back to the whileloop and waits until a connection is made and gate.Set() is called so it can listen for new connections again. So this will allow multiple clients at the same time (Especially if the calculations can take long time)
But should I use stream.ReadAsync() or stream.Read() instead? Im curious if it even matters because I'm already in an asynchronous function which will not block the main thread.
So my final questions are:
UPDATE FOR NEW IMPROVEMENTS
Due to an answer, I have updated my code to this:
private static ManualResetEvent gate = new ManualResetEvent(false);
static async Task ListenToClient(TcpListener listener)
{
//Same Code
}
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 8888);
listener.Start();
while (true)
{
gate.Reset();
Task task = ListenToClient(listener);
task.ContinueWith((Task paramTask) =>
{
//Inspect the paramTask
});
gate.WaitOne(); //Blocks the main thread and waits until the gate.Set() is called
}
}
Upvotes: 3
Views: 266
Reputation: 171178
Is this the best/right way to accomplish this task (also by using ManualResetEvent class)?
No. You start an async operation, then immediately wait for it. For some reason I often see this crazy dance. Just make it synchronous:
while (true) {
var clientSocket = Accept();
ProcessClientAsync(clientSocket);
}
So simple.
Would there be any difference in this scenario to use async or non async operations when reading and writing to the stream?
Using async IO with sockets is good if you have very many clients. For a few dozens at a time you can just use sync IO with threads. Async IO is about not blocking threads (which each use 1MB of stack space).
If you decide to use async IO, ProcessClientAsync
should be an async function like you have it now.
If you decide for sync IO, start ProcessClientAsync
on a new thread to be able to process multiple clients simultaneously.
If it lags, and takes 1-2 seconds to send/receive the data, would it still matter to choose between async and nonasync operations?
As long as you process individual clients independently, you are fine. The choice between sync and async only comes into play at high scale (more than dozens of connections open at the same time).
It is a common mistake to overcomplicate things by going async without the need for it. Basically all tutorials make this mistake.
Upvotes: 2
Reputation: 218827
Right off the bat I see two common async
mistakes:
async void
Don't do this. The only reason the compiler even supports async void
is for handling existing event-driven interfaces. This isn't one of those, so here it's an anti-pattern. async void
effectively results in losing any way of ever responding to that task or doing anything with it, such as handling an error.
Speaking of responding to tasks...
ListenToClient(listener);
You're spawning a task, but never examining its state. What will you do if there's an exception within that task? It's not caught anyway, it'll just be silently ignored. At the very least you should provide a top-level callback for the task once it's completed. Even something as simple as this:
ListenToClient(listener).ContinueWith(t =>
{
// t is the task. Examine it for errors, cancelations, etc.
// Respond to error conditions here.
});
Upvotes: 3