1

I have an old piece of code (.NET 3.5) where I run in a separate thread the registration of new TcpClient:

Old code:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public void StartListening()
    {
        _tcpListener.Start();

        var t = new Thread(new ThreadStart(AcceptConnections));
        t.Start();
    }
    
    private void AcceptConnections()
    {
        while (true)
        {
            var client = _tcpListener.AcceptTcpClient();
            
            // Some stuff
        }
    }
}

The goal of this code is to avoid blocking the app while waiting for new clients. I am porting this code to .NET Standard and I wonder whether I have to use the new features of .NET.

I am talking about async/await and Task.

Async Thread version

Since this thread will last as long as the whole application, I first considered to simply replace the actual method by the new asynchronous one. But if I do so, I end up with a new thread running an async void method:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public void StartListening()
    {
        _tcpListener.Start();

        var t = new Thread(new ThreadStart(AcceptConnectionsAsync));
        t.Start();
    }
    
    private async void AcceptConnectionsAsync()
    {
        while (true)
        {
            var client = await _tcpListener.AcceptTcpClientAsync();
            
            // Some stuff
        }
    }
}

It is not recommended to write async void method except in rare case (like event handler).

Is this case acceptable?

Async task version

As a new option, I run the registration in a Task:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public Task StartListeningAsync()
    {
        _tcpListener.Start();

        return Task.Run(async () => await AcceptConnectionsAsync());
    }
    
    private async Task AcceptConnectionsAsync()
    {
        while (true)
        {
            var client = await _tcpListener.AcceptTcpClientAsync();
            
            // Some stuff
        }
    }
}

With this code, as long as I do not await the AcceptConnectionsAsync call, the app is not blocked (I simplify the code with a while(true) but I handle the closure of the app and the end of the listening properly, I can call the await there or even fire-and-forget it).

But as I said, this action is supposed to last as long as the whole application, I think a new thread is more appropriate (in term of architecture at least but also in term of efficiency).

Am I wrong?

Do not change anything to this part of the code

This is the last option I see. I intended to update the code with the Async/Await because I read that the async/await improves the performance of the TcpListener.

But is that true?

Important information

Here are some information I think can be relevant. Do not hesite to ask for further details.

  • The application running this code is a Console App. I know this can be important regarding to async/await context?
  • The AcceptConnections(Async) method runs a new Task for each new connection (these tasks are short-lived and of type fire-and-forget).
  • The main thread performs huge calculations. The best solution is the least impacting regarding to the main thread performance.
  • There is one other thread that behave like this one (same duration), responsible for listening the data coming from all clients. I intend to apply the same operations as this one.
Community
  • 1
  • 1
fharreau
  • 2,105
  • 1
  • 23
  • 46
  • 1
    `Is this case acceptable?` Is it an event handler? Why do you wish to use `async void` rather than `async Task`? Does https://stackoverflow.com/questions/44364092/is-it-ok-to-use-async-with-a-threadstart-method help? – mjwills Jun 20 '18 at 13:56
  • Because the `ThreadStart` constructor only accept delegates returning `void`. – fharreau Jun 20 '18 at 13:59
  • Why do you wish to use `ThreadStart` vs `Task`? – mjwills Jun 20 '18 at 13:59
  • Didn't find your link in my research ... Too bad, it answers a part of my question. Concerning your second question, I read that `Task` is not intended for long-lived action. That's why I wished to use a `Thread` instead. – fharreau Jun 20 '18 at 14:04
  • 1
    See https://stackoverflow.com/questions/37607911/when-to-use-taskcreationoptions-longrunning @fharreau – mjwills Jun 21 '18 at 07:49

1 Answers1

1

But as I said, this action is supposed to last as long as the whole application

No, it doesn't. The method will return almost immediately. That's what it means for a method to be asynchronous. It doesn't block the caller while the work is being done.

Creating a new thread just to call an asynchronous method is like hiring someone to come to your house to put a letter in your mailbox for you. It's more work than just doing it yourself, because the actual act of getting the letter to its destination doesn't actually prevent you from doing work outside of a trivial bit of work to just get it started.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • It is the whole `AcceptConnectionsAsync` methods which will last as long as the application. The call to `AcceptTcpClientAsync` is within a foreach loop. – fharreau Jun 20 '18 at 14:03
  • @fharreau The method will be doing work *asynchronously* for quite some time. Possibly as long as the application is going on, sure. But the method *returns* immediately. The only work that you're actually running in another thread is the small bit of work to *start* the operation. None of the rest of it is affected in any way. – Servy Jun 20 '18 at 14:04
  • I see what you mean. I can't await that call in the main thread since I need to do other things in that thread (and I need to await it for each new connection). That's why I initially run the method with the blocking call in a new thread. May be I just can't use Async/Await in my case? – fharreau Jun 20 '18 at 14:09
  • @fharreau If you don't want the rest of the code to run after the method never finishes, then don't `await` it. `await` is just an easy way to write code that will run when the asynchronous method finishes. You don't need to create a new thread just to not await something. – Servy Jun 20 '18 at 14:15
  • In that case, where is executed the code after `var client = await _tcpListener.AcceptTcpClientAsync();` within the `AcceptConnectionsAsync` method? In another thread? Since this code is in a forever loop, it won't affect the performance of the main thread? – fharreau Jun 21 '18 at 15:42
  • @fharreau Since you don't have a synchronization context, it'll run in a thread pool thread. If you set a synchronization context, it'll run wherever that context tells it to run. – Servy Jun 21 '18 at 15:44
  • Let me get this straight: I will have my app runing in the main thread, and the forever loop in a thread pool thread. There is no contraindication to run a long-lived process in a thread pool thread? – fharreau Jun 21 '18 at 15:52
  • @fharreau No, you do not have a loop running forever in a thread pool thread. You have an asynchronous loop that occasionally schedules a bit of work to run in a thread pool thread, but most of the time involves no code running on any thread. – Servy Jun 21 '18 at 15:54