Introduction
The new support for asynchronous functions in C# and Vb.Net 5.0 allow an efficient way to deal with concurrent operations represented as Task<TResult>
. The big advantages in daily use are composability and automatic marshaling to the synchronization context (i.e. main thread). The latter saves a lot of calls to Dispatcher.BeginInvoke
and their likes and makes the application virtually single threaded and by that easy to debug and maintain.
We can not only utilize these tools for short asynchronous tasks, but also for potentially endless ones. In this article I will use the TAP infrastructure to create an asynchronous Ping tool.
The TAP and IProgress
First of all we will have a look at our tools. If you are already familiar with Async Functions and the TAP you might want to skip this paragraph.
The new Async Functions in .Net consist of two parts. One is the extension of the compiler. This is surfaced by the introduction of two new keywords: async and await (resp. Async and Await in VB.Net). The usage of those is beyond the scope of this article, but Joseph Albahari’s talk gives a good introduction to those.
The other part is the Taskbased Asynchronous Pattern or TAP. The TAP defines the interface that asynchronous functions should implement. This paper of Stephen Toub describes the pattern in detail.
It states, that an asynchronous function should supply the following signature
Task<TResult> DoWorkAsync(… parameters, CancellationToken cancellationToken, IProgress<T> progress)
The following points should be noted:
- The method returns a
Task
- 'Async' is appended to the methods name
- The method accepts a
CancellationToken
- The method accepts an
IProgress<T>
For our task to create a continuous ping operation we are interested in the last two items.
We might need the ability to cancel the task at some time, so we will want to pass a CancellationToken
. Furthermore we need a way to continuously report ping times. We achieve this by using IProgress<T>
.
IProgress<T> and Progress<T>
As we saw, the .Net Framework 4.5, which is shipped with C# 5, provides an interface IProgress<T>
. This interface contains one single method.
public interface IProgress<in T>
{
void Report(T value);
}
As the name suggests, the asynchronous method can use that method to report progress.
The Framework also includes a simple implementation of that interface named Progress<T>
. This class offers a constructor that accepts an Action<T>
and an event ProgressChanged
. Both can be used to register a callback for process updates. The Progress
class invokes both the action and the event on the synchronization context, if one is present. Such a context is created automatically for every WPF, Silverlight and Forms application. That means that we do not have to care about form.Invoke
resp. Dispatcher.BeginInvoke
ourselves to update UI elements.
Asynchronous Ping using Progress<T>
Using all that, we can finally start to write our async ping.
PingResult
First we need a class to report the progress of our Ping. That class is a container for miscellaneous information of the pings state.
public class PingResult
{
public IPAddress Address { get; private set; }
public int PingsTotal { get; private set; }
public int PingsSuccessfull { get; private set; }
public TimeSpan AverageTime { get; private set; }
public TimeSpan LastTime { get; private set; }
public IPStatus LastStatus { get; private set; }
public PingResult(IPAddress address)
{
Address = address;
LastStatus = IPStatus.Unknown;
}
internal void AddResult(PingReply res){ ... }
}
The AddResult
method will be called by the ping method to accumulate the information into AverageTime
, PingsTotal
and so on. It accepts an instance of System.Net.NetworkInformation.PingReply
.
The Ping methods
With that in place we can at last create the method I keep talking about since the beginning. The method is a thin wrapper around the functionality provided by System.Net.NetworkInformation.Ping
.
public static async Task ContinuousPingAsync(
IPAddress address,
int timeout,
int delayBetweenPings,
IProgress<PingResult> progress,
CancellationToken cancellationToken)
{
var ping = new System.Net.NetworkInformation.Ping();
var result = new PingResult(address);
while (!cancellationToken.IsCancellationRequested)
{
var res = await ping.SendPingAsync(address, timeout).ConfigureAwait(false);
result.AddResult(res);
progress.Report(result);
await Task.Delay(delayBetweenPings).ConfigureAwait(false);
}
}
That's all. If you think about the old days of APM with its BeginPing(...)
and EndPing(IAsyncResult result)
you might be surprised how simple it is to compose asynchronous methods. But let's get through that step by step.
I decided to define that method as static, since all states (result
and ping
) are hidden inside. As explained above, the method returns a Task to give the caller a handle to the running computation. That makes exception handling easy for the caller. An IProgress<PingResult>
object is used to report the ping status. The method can be aborted using the CancellationToken
.
Two async functions are invoked and awaited for within the while loop. Since no code is invoked from this loop that can potentially access UI elements, we use .ConfigureAwait(false)
to signal the compiler that it does not have to continue on the captured synchronization context (i.e. the UI thread). All reporting us done using the Progress
class, which takes care of the marshaling to the UI thread.
WPF Sample application
The source code also includes a simple WPF MVVM app demo application.
You can enter addresses of computers you want to ping. DNS resolution and ping are done asynchronously using this article's methods.
Using the code
The main library contains one static class with three methods to bring asynchronous ping functionality.
namespace Network
{
public static class Ping
{
public static IPAddress ResolveAddress(string hostNameOrAddress)
{...}
public static async Task<IPAddress> ResolveAddressAsync(string hostNameOrAddress)
{...}
public static async Task ContinuousPingAsync(
IPAddress address,
int timeout,
int delayBetweenPings,
IProgress<PingResult> progress,
CancellationToken cancellationToken)
{...}
}
}
ResolveAddress
and ResolveAddressAsync
mimic the behaviour of System.Net.NetworkInformation.Ping
for address resolution. In short you can provide an IP address as string or a hostname that will be resolved using DNS.
ContinuousPingAsync
is used to start the continuous ping I keep talking about.
The interface is fairly simple. Please see the included WPF demo application for details. If you have any problems, feel free to contact me.
History
- 2012-10-21: Initial version
- 2012-10-25: Added source code
- 2014-01-15: Fixed code listing