Introduction
First of all, I'd like to say this article is for beginners only as experienced people may feel this is too simple, let me point that first . Come on, let's begin. Everybody enumerates a sequence(collection) but, sometimes you need a delay between each iteration while enumerating, we'll see how can we implement that with various versions of C#
Background
The idea of writing this as an article arose from this Stackoverflow question, I felt that I'd turn that answer to an article so that future readers do not need to Reinvent the wheel. I am not sure whether I myself am Reinventing the wheel.
Using the Code
Bad Code
We'll begin with bad code.
private void EnumerateWithDelay(IEnumerable<object> sequence, Action<object> action)
{
foreach (var item in sequence)
{
action(item);
Thread.Sleep(1000);
}
}
What's wrong with the above code? Why is it bad? It is not that it won't work. It works, it will iterate over the sequence and adds a delay of 1 second for demonstration purposes, can be anything. The reason it is considered as bad is because it blocks the thread which is never a good idea. What if you need to call this method from UI thread? UI will freeze and obviously no user would like that.
Let's see how can we write this without blocking the thread.
With C# 5.0
C# 5.0 with .NET 4.5 provides a great feature async/await which makes life easier. I'm going to use that feature to implement functionality.
private async void EnumerateWithDelayAsync(IEnumerable<object> sequence, Action<object> action)
{
foreach (var item in sequence)
{
action(item);
await Task.Delay(1000);
}
}
I am not going to explain much about async/await... there are already tons of articles available. What the above code does is it enumerates a sequence, for each iteration it awaits(asynchronously waits) for a second. This method doesn't block the calling thread while it waits. Pretty simple, isn't it?
So what's next? We saw the implementation of our requirement in C#5.0. What if we need to implement this before C# 5.0 where these async/await features are not available? Don't worry, let's do that now.
With C# 4.0
C# 4.0 and .NET 4.0 required for the following code to compile. Note that I used .NET 4.0 just to represent the operation as a Task
so that you may wait for it or attach continuation or whatsoever. Probably .NET 2.0 is enough.
Implementation relies on the fact that Timer
s doesn't block the calling thread till they elapse and that's the most commonly followed way of implementing this kind of things. The fact is that Task.Delay
method in TPL(Task parallel library) in .NET 4.5 is implemented using Timer
only.
So we need a sequence, Action<T>
and delay to wait between iterations. Let's wrap this in a class as we need to access some state. PeriodicEnumerator<T>
is a class that we will be implementing now, which defines a constructor with necessary parameters and it simply stores them in a field for future use.
Enumerate
is the method which actually starts the enumeration. It just gets the actual enumerator from sequence, starts the timer with no given delay to start it immediately and returns a not completed task.
Note that the second parameter is timer. Change is Timeout.Infinite
which means that timer will be fired exactly once and not repeatedly.
public Task Enumerate()
{
enumerator = sequence.GetEnumerator();
timer.Change(0, Timeout.Infinite);
return completionSource.Task;
}
Our important method here is TimerCallback
which actually does the iteration.
private void TimerCallback(object state)
We check to see whether any more elements are available in sequence? If not, we just set the task status as completed, dispose the timer and get back.
if (!enumerator.MoveNext())
{
completionSource.SetResult(null);
timer.Dispose();
return;
}
If elements are available, then we get the current element and invoke the action
again to set the timer but this time with a delay, this process continues till we reach the end of sequence.
T current = enumerator.Current;
if (synchronizationContext != null)
{
synchronizationContext.Send((x) => action(current), null);
}
else
{
action(current);
}
timer.Change(period, Timeout.Infinite);
You might have noticed that I've used something called SynchronizationContext
that gives support for executing the callback in a particular context. That will be useful when you're dealing with UI apps.
Full Code
public class PeriodicEnumerator<T> : IDisposable
{
private IEnumerable<t> sequence;
private Action<t> action;
private int period;
private System.Threading.Timer timer;
private SynchronizationContext synchronizationContext;
private IEnumerator<t> enumerator;
private TaskCompletionSource<object> completionSource = new TaskCompletionSource<object>();
public PeriodicEnumerator(IEnumerable<t> sequence, Action<t> action, int period)
: this(sequence, action, period, null)
{
}
public PeriodicEnumerator(IEnumerable<t> sequence, Action<t> action, int period, SynchronizationContext synchronizationContext)
{
if (sequence == null)
{
throw new ArgumentNullException("sequence");
}
if (action == null)
{
throw new ArgumentNullException("action");
}
if (period <= 0)
{
throw new ArgumentOutOfRangeException("period", "period should be greater than zero.");
}
this.sequence = sequence;
this.action = action;
this.period = period;
this.synchronizationContext = synchronizationContext;
this.timer = new System.Threading.Timer(TimerCallback);
}
public Task Enumerate()
{
if (this.enumerator != null)
{
throw new InvalidOperationException("Enumeration already started");
}
enumerator = sequence.GetEnumerator();
timer.Change(0, Timeout.Infinite);
return completionSource.Task;
}
private void TimerCallback(object state)
{
if (!enumerator.MoveNext())
{
completionSource.SetResult(null);
timer.Dispose();
return;
}
try
{
T current = enumerator.Current;
if (synchronizationContext != null)
{
synchronizationContext.Send((x) => action(current), null);
}
else
{
action(current);
}
timer.Change(period, Timeout.Infinite);
}
catch (Exception ex)
{
completionSource.SetException(ex);
timer.Dispose();
}
}
public void Dispose()
{
timer.Dispose();
}
}
Usecase:
static void ConsoleAppSample()
{
var periodicEnumerator = new PeriodicEnumerator<int>(Enumerable.Range(1, 5), (x) => Console.WriteLine(x), 1000);
Task enumerationTask = periodicEnumerator.Enumerate();
enumerationTask.Wait();
Console.WriteLine("Completed");
Console.Read();
}
static void SynchronizationContextSample()
{
var periodicEnumerator = new PeriodicEnumerator<int>(Enumerable.Range(1, 5), (x) => textbox.Text = x.ToString(), 1000,SynchronizationContext.Current);
Task enumerationTask = periodicEnumerator.Enumerate();
}
Some Uses of PeriodicEnumerator
This piece of code looks pretty simple, isn't it? Simple yet powerful. You can pretty much do whatever with this. Let's see some usages of this code.
Showing the Current Time
It is very common to show current time in our application, typically in a label, or status bar, I'll show you how we can implement that feature with the help of our PeriodicEnumerator
.
private PeriodicEnumerator<DateTime> updateTimeEnumerator;
private void StartUpdatingTime()
{
if (updateTimeEnumerator == null)
{
updateTimeEnumerator = new PeriodicEnumerator<DateTime>(GetCurrentTimeSequence(),
(time) => lblCurrentTime.Text = string.Format("Current time: {0}", time),
1000,
SynchronizationContext.Current);
updateTimeEnumerator.Enumerate();
}
}
We just decalred a PeriodicEnumerator<DateTime>
and initialized it with sequence GetCurrentTimeSequence
which is IEnumerable<DateTime>
, an <code>Action<DateTime>
which updates a label, delay of 1000 ms(1 second) to update the time as it changes, and finally UI SynchronizationContext
as we're going to update the UI.
I can hear you saying: wait! What is that strange method GetCurrentTimeSequence
which you have not shown us? Well, nothing to show in that method. That's just a never ending sequence which is implemented as below:
private IEnumerable<DateTime> GetCurrentTimeSequence()
{
while (true)
{
yield return DateTime.Now;
}
}
The above method uses a language feature called Iterators. You can read about it if you're not aware of it already.
That's it. We're done! The output of the above code rendered in Windows form will look like this:
You can see time starts updating in the status bar, it will continue to do forever.
Animating Controls
In Winforms animations are typically implemented with timers, our PeriodicEnumerator<T>
already wraps a timer, that made it very easy for us to implement animations. I'll show you how to implement a never ending animation (just moving a control).
Let's begin by creating an instance of our PeriodicEnumerator<T>
and required members.
private PeriodicEnumerator<Point> moveEnumerator;
private void StartMovingButton()
{
if (moveEnumerator == null)
{
moveEnumerator = new PeriodicEnumerator<point>(GetNextPointSequence(),
(location) => btnStartMovingMe.Location = location,
10,
SynchronizationContext.Current);
moveEnumerator.Enumerate();
}
}
This time, we have PeriodicEnumerator<Point>
since Control.Location
property is of type Point
. In the above code, we create an instance of new PeriodicEnumerator
with an IEnumerable<Point>
implemented with Iterators, a delegate to update button's Location
, 10 millisecond delay for timer, and the SynchronizationContext
as well which you might be knowing by this time why we need it.
We start the enumeration by calling Enumerate()
.
private IEnumerable<Point> GetNextPointSequence()
{
const int MoveIncrement = 2;
Direction xDirection = Direction.Forward;
Direction yDirection = Direction.Forward;
Size buttonSize = btnStartMovingMe.Size;
while (true)
{
Point newLocation = btnStartMovingMe.Location;
newLocation.X += (xDirection == Direction.Forward) ? MoveIncrement : -MoveIncrement;
newLocation.Y += (yDirection == Direction.Forward) ? MoveIncrement : -MoveIncrement;
if (newLocation.X + buttonSize.Width > btnStartMovingMe.Parent.ClientSize.Width)
{
xDirection = Direction.Reverse;
}
else if (newLocation.X < 0)
{
xDirection = Direction.Forward;
}
if (newLocation.Y + buttonSize.Height > btnStartMovingMe.Parent.ClientSize.Height)
{
yDirection = Direction.Reverse;
}
else if (newLocation.Y < 0)
{
yDirection = Direction.Forward;
}
yield return newLocation;
}
}
private enum Direction
{
Forward = 1,
Reverse = 2,
}
GetNextPointSequence
method does nothing other than returning next point(Location of control) with couple of checks whether control goes outside of parent's bounds; if yes, it just reverses the direction of motion.
So we're done with code, ready to animate; Let's see how that is going to render like.
Animation looks bad because of image quality, it will be fine when you run the EXE.
That's all guys, hope you enjoyed. See you again in another article soon. Critiques and suggestions are welcome.
Points of Interest
It would be easy to implement the cancellation using CancellationToken
, and converting this code to .NET 2.0 using APM. I leave it as an exercise. Try it out.
You may find the following articles helpful which are relevant to the subject:
History
- 02/03/2014 - Initial version
- 03/03/2014 - Some uses of periodic enumerator added
- Updating current time in status bar
- Animating winform controls
- IDisposable interface implemented
- Downloadable source and demo project added.