So, I have to create a service that will execute Ping command to the specified host in a background thread and return a message with Ping command results. As I don’t really need to ping the desired host thousand times per second, the service should be taught to take a break for a while between requests.
What information will I need from the service? I guess, the following will be enough:
public class PingServiceMessage : EventArgs { public string ErrorMessage { get; private set; } public long RoundtripTime { get; private set; } public IPStatus Status { get; private set; } }The service will have the simplest API one've ever seen:
public class PingService : IDisposable { public event EventHandler<PingServiceMessage> PingCompleted; public void Start() {} public void Stop() {} public void Dispose() {} }What about the asynchronous implementation? The straightforward solution would be the usage of BackgroundWorker class per Ping service. It would take a delegate, execute it and return a result and then would sleep for a while without releasing its thread. Alex Ilyin & Alexander Nickolaev, my colleagues, would be indignant with such a solution. And you know, they would be definitely right. The solution is obviously resource consuming and is not robust.
A good alternative is System.Threading.Timer class that provides a developer with the possibility to run a given method in a thread, temporarily taken from the ThreadPool. In this case a thread is being taken only for operation execution time and is released immediately after the operation is finished. This strategy is less resource consuming and more stable, because whenever an operation fails, the service won’t suffer and another thread will be taken next time.
So, in order not to be beaten by my vigilant colleagues I’ve chosen the second option and implemented the service with the timer component. The entire implementation could be found in the public repository, I’ve only taken part of it:
public class PingService : IDisposable { private readonly string target; private readonly Ping ping; private Timer timer; public event EventHandler<PingServiceMessage> PingCompleted; public void Start() { timer = new Timer(Run, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)); } public void Stop() { if (timer != null) timer.Dispose(); } private void Run(object state) { PingServiceMessage message; try { var result = ping.Send(target, 1000); message = new PingServiceMessage(result); } catch (PingException e) { message = new PingServiceMessage(e.Message); } if (PingCompleted != null) { PingCompleted(this, message); } } public PingService(string target) { this.target = target; ping = new Ping(); } }What is interesting here? Timer is created only in Start method. It is given the ‘Run’ method and is told to execute it every 2 seconds and wait for 1 second before the first time. Instance of Ping class is created in PingService constructor. It can be easily reused with a sequence of ping commands providing that Ping command timeout will be less than timer value. In my case, the timeout is set to 1000 ms, while timer value is set to 2 seconds. Seems to be OK. As there is no legal way to stop the timer other than call ‘Dispose’ on it, the ‘Stop’ method does exactly that. ‘Run’ method tries to execute Ping command with timeout set to 1000 ms and catches PingExceptions, if any. Finally, it notifies PingService listeners by sending a new instance of PingServiceMessage that might contain information about Ping command result or an exception message.
Let’s check whether the service works. To do so, I’ve added a simple console application and used the service there:
class Program { static void Main(string[] args) { var service = new PingService("192.168.1.1"); service.PingCompleted += PingCompleted; service.Start(); Console.ReadKey(); } static void PingCompleted(object sender, PingServiceMessage e) { var service = sender as PingService; Console.WriteLine(string.Format("Reply from {0}: time={1} ms", service.Target, e.RoundtripTime)); } }Here is the result:
Wow, works like a charm! =)
In the next posts I will try connecting all these parts together.
No comments:
Post a Comment