14

For the last few months I have been reading about async-await in C# and how to properly use it.

For the purpose of a laboratory exercise, I am building a small Tcp server that should serve clients that connect to it. The program is a console application.

I use a while loop to wait for connections like so:

while (!_isStopRequested)
{
       TcpClient client = await _tcpListener.AcceptTcpClientAsync();

       await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);
} 

So, until now the method ProcessClientAsync I made was marked as async void and I would just call it ProcessClientAsync(client) and the call would immediately return to the caller. However I read on the internet that it is a poor decision to use it unless it is for an event. So I changed the definition to async Task.

Ok, but without an await, I get a warning in Visual studio "Because this call is not awaited, the current method continues to run before the call is completed".

And once I used await ProcessClientAsync(client), the code doesn't work. I can connect only one client, and then the caller "waits" for ProcessClientAsync to return. However, that method has a infinite loop and will not return, but I need the while loop to continue processing the next client.

Googling I came up to this thread: How to safely call an async method in C# without await

I guess the questions are pretty much the same, except that when I use await ProcessClientAsync, I want it to return to the caller immediately, which it doesn't do, I know because running the second client, while the first is still running, the second client doesn't connect.

So I did this:

await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);

But since ProcessClientAsync has to return a Task, I am not sure if this is Ok to do?

That would be one question.

The other would be: how could I call a async Task method that will run forever, and have the call return to the caller immediately so the while loop can continue and the tcp listener can continue to accept the next client?

Thank you.

Sorry if it is a repetition, or unclear what I am asking.

Community
  • 1
  • 1
Marin
  • 861
  • 1
  • 11
  • 27
  • Please narrow down your question: it's overly broad. Plus, for better readability do some editing excluding non-essential part. Thanks and regards, – Alexander Bell Jun 07 '15 at 19:03

3 Answers3

19

For the purpose of a laboratory exercise, I am building a small Tcp server that should serve clients that connect to it. The program is a console application.

Well, the very first thing I'd recommend is to try a simpler exercise. Seriously, an asynchronous TCP server is one of the most complex applications you can choose. The vast majority of developers using async are working on apps two orders of magnitude simpler.

Instead of that, try writing a UI app that will download a file from a URL. That's a more realistic starting point for learning async.

But since ProcessClientAsync has to return a Task, I am not sure if this is Ok to do?

I'd recommend using Task.Run instead of Task.Factory.StartNew. Task.Run is aware of async methods, whereas StartNew is not.

However, that's assuming that kicking off a thread is the correct operation in the first place. Most TCP/IP server applications are not CPU-bound, so I question the use of multiple threads here. It's entirely possible to have a fully-asynchronous TCP/IP server that only uses the thread pool for its async method continuations.

The other would be: how could I call a async Task method that will run forever, and have the call return to the caller immediately so the while loop can continue and the tcp listener can continue to accept the next client?

You just don't await the Task it returns:

Task.Run(() => ProcessClientAsync(client));

However, the compiler will complain here, because this code is almost certainly wrong. If you ignore the returned task, then you're ignoring any exceptions from that code.

A common pattern in TCP/IP server apps is to maintain a small collection of state for each connected client. That's a natural place to put both the socket object itself and the task that represents the handling of that socket connection:

var clientState = new ClientState(client);
clientState.Task = Task.Run(() => ProcessClientAsync(client));

These client states are then stored in a list/dictionary. How exactly you do this is of course up to you.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you for the comprehensive answer. I tried out your suggestions and made a class ClientState which accepts a tcp client. I then did clientState.Task = Task.Run(() => ProcessClientAsync(client)); as you suggested, but it behaves just as it did when I had await.. I put that piece of code inside the while loop. – Marin Jun 07 '15 at 20:47
  • @Marin: If you don't `await` the `Task` (and don't block on it either), then the main thread will continue executing. – Stephen Cleary Jun 08 '15 at 02:10
  • thank you, you were right, the error was somewhere else! A very strange error... I decided to put client states into concurrent bag, and had the Add method for adding, but forgot to instantiate it... strangely, I didn't get a null reference exception?! how is this possible?! all in all it works now... thanks! – Marin Jun 13 '15 at 12:36
2

This will start the task and return to the caller, and avoid the compiler warning:

var t = Task.Factory.StartNew(() => {});
glenebob
  • 1,943
  • 11
  • 11
0

If I'm correct understand your problem then this code should help:

private async void AcceptClient(TcpClient client)
{
  await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);
}

private async void Run()
{
  while (!_isStopRequested)
  {
       TcpClient client = await _tcpListener.AcceptTcpClientAsync();
       AcceptClient(client);
  }
} 
Oleg
  • 1,378
  • 11
  • 22
  • OP switched from void to Task per guidelines, but I'd argue this scenario fits the guideline for async void methods just fine. – glenebob Jun 07 '15 at 19:47
  • It did cross my mind that maybe it would be ok to use async void, but I wanted to make it "by the book" , hence changed to async Task – Marin Jun 07 '15 at 20:50