3

Can someone tell me what the best practice/proper way of doing this is?
I'm also using WPF, not a console or ASP.NET.

Using Listener to accept clients and spin off a new "thread" for each client that handles all the I/O and Exception catching for that client.

Method 1: Fire and forget, and just throw it into a variable to get rid of the warning.

public static async Task Start(CancellationToken token)
{
    m_server = TcpListener.Create(33777);
    m_server.Start();
    running = true;
    clientCount = 0;

    // TODO: Add try... catch
    while (!token.IsCancellationRequested)
    {
        var client = await m_server.AcceptTcpClientAsync().ConfigureAwait(false);

        Client c = new Client(client);

        var _ = HandleClientAsync(c);
    }
}

Here's the Client Handler code:

public static async Task HandleClientAsync(Client c)
{
    // TODO: add try...catch
    while (c.connected)
    {
        string data = await c.reader.ReadLineAsync();

        // Now we will parse the data and update variables accordingly
        // Just Regex and some parsing that updates variables
        ParseAndUpdate(data);
    }
}

Method 2: The same thing... but with Task.Run()

var _ = Task.Run(() => HandleClientAsync());

Method 3: an intermediate non async function (doubt this is good. Should be Async all the way)
But this at least gets rid of the squiggly line without using the variable trick which kinda feels dirty.

while (!token.IsCancellationRequested)
{
    var client = await m_server.AcceptTcpClientAsync().ConfigureAwait(false);

    Client c = new Client(client);

    NonAsync(c);
}

public static void NonAsync(VClient vc)
{
    Task.Run(() => HandleClientAsync(vc));
}

Method 4: Make HandleClientAsync an Async void instead of Async Task (really bad)

public static async Task HandleClientAsync(Client c)
// Would change to
public static async Void HandleClientAsync(Client c)

Questions:

  • Is it any better to use Task.Run() When doing a fire and forget task?
  • Is it just accepted that you need to use the var _ = FireAndForget() trick to do fire and forget? I could just ignore the warning but something feels wrong about it.
  • If I wanted to update my UI from a Client, how would I do that? Would I just use a dispatcher?

Thanks guys

Xander Luciano
  • 3,753
  • 7
  • 32
  • 53
  • You shouldn't be using a thread to handle I/O-bound tasks, IOCP don't require threads. Spinning up a thread is a waste. –  Feb 18 '17 at 03:17
  • @MickyD so then how would I get the data from client 3 if client 1 and 2 are idle? If I am awaiting data from their streamreaders then I'm "blocking" until I get that data. If I use `WhenAll()` then I can't get data from client 3 more than once. I don't see what other option there is though. – Xander Luciano Feb 18 '17 at 03:22
  • What makes you think `await` is _"blocking"_? –  Feb 18 '17 at 03:26
  • 1
    if I `await stream1.readLineAsync()` and have `await stream2.readLineAsync()` then if stream 2 completes I won't have it's result until after stream 1 completes and returns. I can first check if the stream has data available and then return early if it doesn't, but if I do await, it'll just wait. – Xander Luciano Feb 18 '17 at 03:30
  • Perhaps you should look into TPL DataFlow? Honestly I thought your question was _general_ in nature. Perhaps you need to ask a _new question_? –  Feb 18 '17 at 03:36
  • I tried asking the specific question before :( but didn't get much help and basically went back to googling and trying different things. Now I'm just trying to figure out which way is best. At this point I'm feeling like if it works, it works. http://stackoverflow.com/questions/42116277/how-to-use-task-whenany-with-readlineasync-to-get-data-from-any-tcpclient – Xander Luciano Feb 18 '17 at 03:39
  • @XanderLuciano: Is there a particular reason you're using sockets instead of SignalR? – Stephen Cleary Feb 18 '17 at 04:16
  • @StephenCleary Yea, this isn't technically a web application but rather inter process communication. I'm using TCP right now but plan to drop down to sockets after I have a better understanding. – Xander Luciano Feb 18 '17 at 04:19
  • @XanderLuciano: I know. You should use SignalR or named pipes for IPC. Generally speaking, take the highest abstraction you can use; it takes a smart person **months** to learn how to use TCP/IP sockets correctly. – Stephen Cleary Feb 18 '17 at 14:12
  • @StephenCleary I hadn't heard of signalR before, looking it I found info on ASP.NET and on "real time web functionality" but I'm WPF and wasn't sure if this is "real time web." Using the highest abstraction level is smart, which is why I'm moving from threads to tasks. The current system uses named pipes, but from what I've read, TCP/Sockets can have the same performance, or better, as well as being more flexible when communicating with other languages. I'm under no time pressure, I just want to learn/improve and create the best "proper" program. http://stackoverflow.com/questions/10872557/ – Xander Luciano Feb 18 '17 at 14:25
  • Named pipes are just as language-agnostic as sockets. – Stephen Cleary Feb 18 '17 at 19:33
  • Yes, you bring up some great points, and looking at pipes, the only language I would run into trouble with would be Javascript. Specifically NodeJS and the Electron Framework. Though, we are currently using named pipes and have trouble when trying to handle multiple clients. However WCF seems to be the solution that issue. The additional flexibility of sockets to work over a network is an attractive feature though. I've found threads that say local sockets bypass the network layer and are just as fast, and others that say they are slower. In my tests I've seen equal perfomance. Thanks Stephen! – Xander Luciano Feb 18 '17 at 20:07

1 Answers1

1

I've never been a fan of background workers which you expect to run for a long time, being run in a task. Tasks get scheduled to run on threads drawn from a pool. As you schedule these long running tasks, the thread pool gets smaller and smaller. Eventually all of the threads from the pool are busy running your tasks, and things get really slow and unmanageable.

My recommendation here? Use the Thread class and manage them yourselves. In this way, you keep your thread pool and the overhead for for tasks out of the picture.

Addendum - Producer Consumer Model

Another interesting question to consider: Do you really need a thread for every client? Threads are reasonably costly to create and maintain in terms of memory overhead, and if your client interaction is such that the client threads spend the vast majority of their time waiting around on something to do, then perhaps a producer consumer model is more suited to your use case.

Example:

  • Client connects on listening thread, gets put in a client queue
  • Worker thread responsible for checking to see if the clients need anything comes along through that queue every so often and checks - does the client have a new message to service? If so, it services all messages the client has, then moves on

In this way, you limit the number of threads working to just the number needed to manage the message queue. You can even get fancy and add worker threads dynamically based on how long its been since all the clients have been serviced.

If you insist

If you really like what you have going, I suggest refactoring what youre doing a bit so that rather than HandleClientAsync you do something more akin to CreateServiceForClient(c);

This could be a synchronous method that returns something like a ClientService. ClientService could then create the task that does what your HandleClientAsync does now, and store that task as a member. It could also provide methods like ClientService.WaitUntilEnd() and ClientService.Disconnect() (which could set a cancellation token, also stored as a member variable)

Ben
  • 2,867
  • 2
  • 22
  • 32
  • Funnily enough.. I am refactoring threaded code into async/await code. I had just read a lot about how Tasks are better so I figured I'd try it out. But I've found a lot of it to be really confusing when it comes to doing stuff like this. – Xander Luciano Feb 18 '17 at 03:19
  • What's the difference between using a thread pool thread and _"Use the Thread class and manage them yourselves"_ for a long-running task? Anyway, you shouldn't be using a dedicated thread for I/O-bound operations. IOCP don't require threads –  Feb 18 '17 at 03:20
  • @MickyD It's about the limitation of resources, why take a thread out of a pool that is meant to be cycled often if youre going to hold it forever - it's akin to renting a book and never returning it when you should buy it. And I agree a dedicated thread is unnecessary, it's implied in my addendum – Ben Feb 18 '17 at 03:25
  • A thread is a thread is a thread. Spinning up 32 long-running threads in _stealth_ by by-passing the thread pool most likely uses just as much memory as a pool thread and has similar impacts on the OS. The problem is isn't where you get the thread from, rather the amount of concurrent active threads at any one time. By the way, even `TaskFactory` defines [LongRunning](https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.110).aspx) –  Feb 18 '17 at 03:34
  • 1
    I'm saddened you think I am "combatting" you? –  Feb 18 '17 at 03:38