Async, non-blocking networking
It should be evident by now that you must ensure that all non-instantaneous operations in your code are a potential performance issue. Slow operations can block a process from continuing. File I/O is one of the areas where this is appropriate. Networking is even slower than that. So, everything that we can do asynchronously should be implemented that way.
The good news is that most classes dealing with networking have asynchronous versions of their methods. The bad news is that for Socket
, it is not as straightforward as you might have hoped. But do not worry: we will tackle this soon!
Making asynchronous calls
In the previous sample, we used the static Dns
class to get information about the address of the NTP server. We called GetHostEntry()
, which is a synchronous blocking call. We can fix that quite easily: Dns
has asynchronous versions of those methods. We can rewrite the call to look like this:
var addresses = await Dns.GetHostEntryAsync(ntpServer);
Of course, the signature of the method needs to change as well. Instead of having this method declaration: public DateTime GetNetworkTime(string ntpServer = "
pool.ntp.org")
.
We change it into this:
public async Task<DateTime> GetNetworkTimeAsync(string ntpServer = "
pool.ntp.org")
We made it async
, changed the return type to Task<DateTime>
instead of DateTime
, and renamed the method to have the Async
postfix.
That was simple enough. We can do the same for the code working with Socket
. This is the full method:
public async Task<DateTime> GetNetworkTimeAsync(string ntpServer = "pool.ntp.org") { // NTP message size - 16 bytes (RFC 2030) var ntpData = new byte[48]; // Setting the Leap Indicator, Version Number, and Mode values ntpData[0] = 0x23; // LI, Version, Mode var addresses = await Dns.GetHostEntryAsync(ntpServer); var ipEndPoint = new IPEndPoint(addresses.AddressList[0], 123); // NTP uses port 123 using (var socket = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { await socket.ConnectAsync(ipEndPoint); await socket.SendAsync( new ArraySegment<byte>(ntpData), SocketFlags.None); await socket.ReceiveAsync( new ArraySegment<byte>(ntpData), SocketFlags.None); } return ConvertNtpTimeToDateTime(ntpData); }
This version takes advantage of the async/await pattern, so the calls to the server do not block the threads.
Tip
Networking code should always use asynchronous methods instead of synchronous ones. Networking is slow compared to the raw speed of the CPU and the local machine, so why waste time waiting on that slow trickle of data from the network adapter?
However, there are ways to improve the performance of your system when you use networking. Let’s look at those next.