Blog Post

An example of repeating code using a worker thread - without using timers c#

Ideas & Innovation
An example of repeating code using a worker thread - without using timers c#
  • 14
    Sep
  • Image

An example of repeating code using a worker thread - without using timers c#


Last month I wrote about how to perform a recurring/repeating tasks using a different mechanism from your standard timers. What was glaringly missing from that article was how could this fascinating feature be used in the real-world.

I have had on my radar for sometime now to create a polling framework where I can pass an action and have that action be called at a set interval. I could easily get this done with the .NET framework however in the real-world we will need a more flexible reusable component.

Let us dive straight in!

We start by creating our contract for our generic polling framework. We are aiming to create a component that simple polls at a designated interval and performs some work. The work and interval must be determined by the caller/client. Also we allow subscribers to be notified that work has been done and provide to them the result of that work.

public interface IPolling
{
	void Start();
	void Stop();
	void SubscribeForPollingUpdates(Action<object> observer);
	void UnsubscribeFromPollingUpdates(Action<object> observer);
}

Now here we have the magic. The code below provides the ability to poll at a specified interval and perform some work, add and remove subscribers and most importantly cleans up after itself.

using System.Threading;

Now that the prerequisite using is out of the way lets get to the real stuff.

public class Polling : IPolling, IDisposable
{
	private readonly IPoller _poller;

	// This will enable the thread to pause and
	// also enable the thread to terminate 
	private readonly ManualResetEvent _shutdownEvent = new ManualResetEvent(false);

	// Maintains if the polling thread is already active
	private bool _isPollingActivated;

	// Thread Synchronisation instance
	private readonly object _syncObj = new object();

	private event Action<object> PollingObservers;

	#region Constructors

	/// 
	/// Ensure that the <see cref="Polling"/> object has something
	/// to do after creation.
	/// Work to be done
	/// 
	public Polling(IPoller poller)
	{
		_poller = poller;
	}

	#endregion

	#region Implementation of IPolling

	/// 
	/// Start the polling
	/// 
	public void Start()
	{
		lock (_syncObj)
		{
			if (!_isPollingActivated)
			{
				// Use worker thread to perform work. This approach
				// caters for the "many", ensuring that most applications
				// will benefit from the work being done on another thread
				// besides the main application thread.
				var worker = new Thread(InternalPolling);
				worker.Start();
				_isPollingActivated = true;
			}
		}
	}

	/// 
	/// Stop the polling
	/// 
	public void Stop()
	{
		lock (_syncObj)
		{
			if (_isPollingActivated)
			{
				_shutdownEvent.Set();
				_isPollingActivated = false;
			}
		}
	}

	#endregion

	#region Private Workers

	/// 
	/// Manages the polling
	/// 
	private void InternalPolling()
	{
		while (_shutdownEvent.WaitOne(_poller.Interval, true) == false)
		{
			object result = _poller.GetResult();
			NotifyObservers(result);
		}
	}

	/// 
	/// Notify the observer that our  has
	/// done some work and they may need to take action.
	/// 
	private void NotifyObservers(object data)
	{
		if (PollingObservers != null)
			PollingObservers(data);
	}

	#endregion

	#region Public Interface

	/// 
	/// Allows the subscription of observers
	/// 
	public void SubscribeForPollingUpdates(Action<object> observer)
	{
		PollingObservers += observer;
	}

	/// 
	/// Allows the unsubsciption of observers
	/// 
	public void UnsubscribeFromPollingUpdates(Action<object> observer)
	{
		PollingObservers -= observer;
	}

	#endregion

	#region Implementation of IDisposable

	public void Dispose()
	{
		Dispose(true);
		GC.SuppressFinalize(this);
	}

	protected virtual void Dispose(bool disposing)
	{
		if (disposing)
		{
			// We need to avoid the lapsed listener problem
			PollingObservers = null;
		}
	}

	#endregion
}

I have included a bit of boilerplate code since this is a real-world example.

  • I ensure that once the polling has started, calling Start again will have no effect until Stop has been called.
  • There is code in the Dispose method to ensure that we do not encounter the lapsed listener problem that so often afflicts applications. Clean up all the subscribers if the unsubscribe method has not been called.
  • I ensure that both the Start and Stop methods cannot be called simultaneously (on separate threads).
  • The InternalPolling method can be adjusted to use any timing mechanism if the “out of the box” (what I have provided) implementation is not sufficient for your needs.

But wait... There is a parameter in the constructor - so where does this come from? To ensure that the user/client has an extensible and pluggable mechanism for determining what the polling action is, I have created the IPoller interface. I had brain freeze when coming up with the names for these classes.

This IPoller component simply exposes a method called GetResult which will be called by the Polling class. The returned data will then be passed on to the observers. The component also exposes the timing Interval for the Polling instance.

public interface IPoller
{
	object GetResult();
	TimeSpan Interval { get; set; }
}

Here we have a concrete implementation of the IPoller interface which retrieves the contents of a URL.

public class UrlPoller : IPoller
{
	private readonly string _url;

	public UrlPoller(string url)
		: this(url, TimeSpan.FromMinutes(5))
	{}

	public UrlPoller(string url, TimeSpan interval)
	{
		if(!Validate(url))
			throw new InvalidDataException(String.Format("Url is not in the proper format: {0}", url));
			
		_url = url;
		Interval = interval;
	}

	private bool Validate(string url)
	{
		bool response = true;

		if (String.IsNullOrWhiteSpace(url))
			response = false;
		else
		{
			try
			{
				var validationComponent = new Uri(url);
				if(!validationComponent.IsAbsoluteUri || !validationComponent.IsWellFormedOriginalString())
					response = false;
			}
			catch (UriFormatException)
			{
				response = false;
			}
		}

		return response;
	}

	#region Implementation of IPoller

	public object GetResult()
	{
		using (var client = new WebClient())
		{
			return client.DownloadString(_url);
		}
	}

	public TimeSpan Interval { get; set; }

	#endregion
}

To cap everything off lets just work out how to use all of this. Create a console application and substitute the code below.

class Program
{
	static void Main(string[] args)
	{
		if (args.Length > 0)
		{
			IPoller poller = new UrlPoller(args[0], TimeSpan.FromSeconds(7));
			IPolling pollingComponent = new Polling.Core.Polling(poller);
			pollingComponent.SubscribeForPollingUpdates(PollingAction);
			pollingComponent.Start();

			// Pause
			Console.WriteLine("Application has started polling at {0}.", DateTime.Now);
			Console.WriteLine("Press any key to stop polling...\n");
			Console.ReadKey();

			// Terminate polling
			pollingComponent.Stop();

			Console.WriteLine("Polling stopped.");
			Console.WriteLine("Press any key to terminate application...");
			Console.ReadKey();
		}
		else
		{
			Console.WriteLine("ERROR: No parameters supplied!");
			Console.ReadKey();
		}
	}

	public static void PollingAction(object data)
	{
		Console.WriteLine("Action called: {0}", DateTime.Now);
		Console.WriteLine("---------------------------------------------------");
		Console.WriteLine(data.ToString().Substring(0, 50));
		Console.WriteLine();
	}
}

A complete commercial polling component in a few lines of code. Please feel free to Like, Plus or Tweet this article. Thank you and goodnight.

Comments (1)

Praveen 18 March 2013, 09:18 PM

Thanks for a really nice example. This gives me an idea. My requirement is like to be able to call any action on this. But i think i can look in to it.

Thanks.

New Comment

Notify me of follow up posts