7

I am at the moment messing around with clients and servers in C# winforms and I'm trying to implement it all asynchronously. However, I'm beginning to wonder, should I use asynchronous code for everything?

Here's a list of what I'm doing asynchronously at the moment:

  1. TcpClient.BeginConnect with TcpClient.EndConnect

  2. NetworkStream.BeginRead with NetworkStream.EndRead

  3. TcpListener.BeginAcceptTcpClient with TcpListener.EndAcceptTcpClient

  4. Listening thread on server for client connections

  5. Listening thread on client for incoming data from server

  6. Listening Task on server for incoming data from each client

  7. New Task created every time an event such as ConnectionLost is raised (so the respective form can update). Think Delegate.BeginInvoke.

Everything is set up asynchrously and it works well, but I'm beginning to wonder if all of these should be asynchronous. I mean, it all sounds nice and people claim it to be efficient due to IO completion ports not blocking or something, but is it really?

I can understand having a single thread for listening on both the client and server, and for reading it makes sense as well. But every time an event is raised (which may be quite often!), should its invocation really be asynchronous? It seems like I am using Tasks for everything that can be made asynchronous and I'm not sure whether or not that is best practice.

6
  • I'm not sure if this is relevant to your project's future or not, but something to consider is that if you ever consider moving your components to Silverlight, you will no longer have a choice in the matter-- you'll have to do all of these things asynchronously.
    – Adam Maras
    Commented Sep 26, 2011 at 21:04
  • @Adam Maras: Good to know but not relevant. Besides, isn't Microsoft deprecating Silverlight or something? Commented Sep 26, 2011 at 21:06
  • Silverlight is still in active development. Microsoft has made it particularly clear that Silverlight and all the .NET technologies will continue to be maintained, even while Microsoft pushes the new WinRT runtime and its various language projections.
    – Adam Maras
    Commented Sep 26, 2011 at 21:12
  • Speaking of Async and WinRT, I recommend This video from the build conference.
    – jornb87
    Commented Sep 26, 2011 at 21:16
  • Yes, makes no sense. Either use BeginXxx or use threads, don't do both. UI updates should by async, there isn't much point in waiting on it. Easily done with Dispatcher.Invoke or Control.BeginInvoke. Commented Sep 26, 2011 at 21:19

2 Answers 2

9

Yes, it really is more efficient to do all this stuff asynchronously. If you do it synchronously then you're constantly polling, which wastes resources and can cause you to miss events if you don't process them quickly enough.

In addition, if you tried to do all that stuff synchronously, you'll find yourself mired in silly synchronization problems, trying to shuffle priorities, weird edge cases that come up when a client tries to connect within 22 msec of another client disconnecting, but only on Thursdays when there's a full moon.

With your current design, each logically separable task is separated from the rest of the program. It was likely easier to write, and it will be a whole lot easier to debug if something goes wrong. And maintenance is easier, too, because it's impossible (okay, very difficult) to get confused and modify the wrong thing. Having worked on systems like this that were not asynchronous, I can attest that it's incredibly easy to think you're diddling the input buffer when in fact you're mangling the output buffer.

As to your final question: yes, you really should raise every event asynchronously. Responding to an event can take an arbitrarily long amount of time. If you write it so that responding to the event is done synchronously, and that response takes a long time (which, in a communications app like this, could be five seconds or less), then you're going to miss events.

The asynchronous model you're using is very much like an implicit queue. It can tolerate spikes in traffic that would overwhelm a synchronous system. As long as, on average, you can process traffic faster than it comes in, you're fine. With a synchronous system, too many signals at once, or too many signals that take a long time to process, will kill the application. With the asynchronous model, the only way to kill the application is to overflow the queue.

5
  • 1
    'If you do it synchronously then you're constantly polling, which wastes resources and can cause you to miss events if you don't process them quickly enough.' Eh? synchronous=polling? Don't really understand this answer at all.
    – Martin James
    Commented Sep 27, 2011 at 0:32
  • 'Having worked on systems like this that were not asynchronous, I can attest that it's incredibly easy to think you're diddling the input buffer when in fact you're mangling the output buffer.' - Never done that in 30 years of multithreaded system development. How could synchronous-vs-asynchronous patterns influence the typing of buffer object instance variable names? I'm as capable as anyone when it comes to screwups, but the choice of I/O design doesn't affct my bug-frequency much: it's always high :(
    – Martin James
    Commented Sep 27, 2011 at 0:41
  • @MartinJames: The synchronous polling problem I mentioned is one where the code polls for a signal, processes it, then polls for the next signal. If processing takes a long time, then signals will be lost. Perhaps the hardware you're interfacing with only latches the signal for a brief period. Or the network interface gets overloaded by too many pending signals. Or the network transport layer times out because you didn't acknowledge the signal fast enough.
    – Jim Mischel
    Commented Sep 27, 2011 at 3:17
  • re: diddling input vs. mangling output. Again, YMMV. I've seen and worked on code where it was incredibly easy to confuse the two. Granted, it was horribly written code, all in assembly language with little documentation and written by a "clever" programmer. Perhaps I'm overly cautious now, after that experience.
    – Jim Mischel
    Commented Sep 27, 2011 at 3:20
  • 1
    "weird edge cases that come up when a client tries to connect within 22 msec of another client disconnecting, but only on Thursdays when there's a full moon." Haha, so true. Commented Nov 22, 2014 at 14:09
0

From the question, it sounds like you have concerns about the performance of asynchronous operations in certain circumstances. Asynchronous operations are incredibly fast and efficient because under the covers .NET uses thread pools: The threads to execute the code already exist and thus there is basically no penalty for picking one of these and executing some code on it.

While on the other hand, the performance gains to be had for async are huge. Synchronous methods will sit on a thread waiting for some I/O operation to complete like a disk read or a network call. Since I/O operations typically take far longer to complete than actual CPU computation, that thread is doing nothing for basically its entire existence. This is bad for two reasons. Threads typically cost around 1MB of memory and when you have a lot of threads around that are all blocking on long-running I/O operations, you get a lot of expensive context switching as threads are swapped in and out of the CPU.

The upshot is that there are basically never any performance problems from using asynchronous methods. The only downside is the increase in code complexity, but the C# async/await pattern coming in C# 5.0 (linked earlier) looks like it's going to basically eliminate the hassle typically associated with async.

3
  • Well, while accepting the advantages of asynchronous kernel-thread-pool approach to I/O, you can't have it both ways. If threads are stuck on long-running I/O, there is no context switching between them. 'Asynchronous' I/O wins on performance, in general, because the blocking calls are moved from user-space to kernel. The downside is that in-line, thread-safe, code has to give way to state machines where socket object methods are called from different threads each time. Many developers struggle with even simple multithreaded code - thread-agnostic state engines? Ok for you and me..
    – Martin James
    Commented Sep 27, 2011 at 0:25
  • 'Threads typically cost around 1MB of memory' - that's 1MB of virtual memory, assuming that the default 'PE'stack is 1MB and the CreateThread call uses the default stack size.
    – Martin James
    Commented Sep 27, 2011 at 0:34
  • You do get context switching if you're processing a lot of blocking calls at once. It's basically guaranteeing that your thread will need a context switch when it comes back. And that means that even if you have all the threads you need to satisfy all the blocking calls, you'll still take the hit of context switching a lot.
    – RandomEngy
    Commented Sep 27, 2011 at 2:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.