Click here to Skip to main content
15,886,137 members
Articles / Programming Languages / C#

Event traffic calming

Rate me:
Please Sign up or sign in to vote.
4.75/5 (3 votes)
26 Jan 2013CPOL6 min read 17K   207   6  
How to build an object to calm down event traffic.
using System;
using System.Collections.Generic;
using System.Threading;

namespace TrafficCalming
{
    /// <summary>
    /// Calms the traffic produced by rapiddly repeating events.
    /// </summary>
    /// <remarks>
    /// When handling some repeating events it might be necesary to slow down traffic, for instance to
    /// show progress in the UI. This service executes events pausing after each execution, new events 
    /// arrived are discarded. It will kind of work like a limited (very) capacity queue.
    /// 
    /// The name comes from "Traffic calming" (http://en.wikipedia.org/wiki/Traffic_calming).
    /// 
    /// Is important to see this objects should not be shared among objects as they will receive 
    /// notifications from the other objects as well as their own.
    /// </remarks>
    public class EventCalmer
    {
        private enum CalmerState
        {
            Idle,
            Executing,
            ExecutingOneInQueue,
            Waiting,
            WaitingOneInQueue,
        };

        private static readonly Dictionary<CalmerState, CalmerState> EventReceivedTransitions =
            new Dictionary<CalmerState, CalmerState>
                {
                    { CalmerState.Idle, CalmerState.Executing },
                    { CalmerState.Executing, CalmerState.ExecutingOneInQueue },
                    { CalmerState.Waiting, CalmerState.WaitingOneInQueue },
                    { CalmerState.WaitingOneInQueue, CalmerState.WaitingOneInQueue },
                    { CalmerState.ExecutingOneInQueue, CalmerState.ExecutingOneInQueue },
                };

        private static readonly Dictionary<CalmerState, CalmerState> DoneOrFinishedTransitions =
            new Dictionary<CalmerState, CalmerState>
                {
                    { CalmerState.Executing, CalmerState.Waiting },
                    { CalmerState.Waiting, CalmerState.Idle },
                    { CalmerState.ExecutingOneInQueue, CalmerState.WaitingOneInQueue },
                    { CalmerState.WaitingOneInQueue, CalmerState.Executing },
                };

        private readonly object _criticalSectionDoor = new object();

        private readonly TimeSpan _sleepTime;

        private DateTime _lastExecutionBeganAt;
        private CalmerState _currentState;

        /// <summary>
        /// Creates an instance
        /// </summary>
        /// <param name="sleepTime">Pause time between executions</param>
        public EventCalmer(TimeSpan sleepTime)
        {
            _sleepTime = sleepTime;
        }

        /// <summary>
        /// Creates an instance
        /// </summary>
        /// <param name="sleepTime">Pause time between executions</param>
        public EventCalmer(int sleepTime)
            : this(TimeSpan.FromMilliseconds(sleepTime))
        {
        }

        /// <summary>
        /// Notifies an event has occurred
        /// </summary>
        public event EventHandler EventOccurred;

        private void OnEventOccurred(EventArgs e)
        {
            _lastExecutionBeganAt = DateTime.Now;
            if (EventOccurred != null) EventOccurred(this, e);
        }

        /// <summary>
        /// HandleEvent to subscribe to the actual event producer.
        /// </summary>
        /// <param name="sender">Event producer</param>
        /// <param name="e">Event args</param>
        public void HandleEvent(object sender, EventArgs e)
        {
            EventReceived();
        }

        private void EventReceived()
        {
            lock (_criticalSectionDoor)
            {
                var newState = GetStateToMoveToWhenEventArrives();
                SetCurrentStateTo(newState);
                
                if (_currentState == CalmerState.Executing)
                    BeginExecutionThread();
            }
        }
        
        private CalmerState GetStateToMoveToWhenEventArrives()
        {
            return EventReceivedTransitions[_currentState] ;
        }

        private void SetCurrentStateTo(CalmerState newState)
        {
            _currentState = newState;
        }

        private void BeginExecutionThread()
        {
            Action executionThread = ExecuteWhileNotIddle;
            executionThread.BeginInvoke(null, null);
        }

        private void ExecuteWhileNotIddle()
        {
            while (_currentState != CalmerState.Idle)
            {
                if (_currentState == CalmerState.Executing || 
                    _currentState == CalmerState.ExecutingOneInQueue)
                    DoExecuteStep();
                else
                    DoWaitStep();
    
                UpdateStateAfterDoneOrFinished();
            }
        }

        private void DoExecuteStep()
        {
            OnEventOccurred(EventArgs.Empty);
        }

        private void DoWaitStep()
        {
            var stillNeedToWait = _sleepTime - GetTimeItTookLastExecution();
            if (stillNeedToWait > TimeSpan.Zero)
                Thread.Sleep(stillNeedToWait);
        }

        private TimeSpan GetTimeItTookLastExecution()
        {
            return DateTime.Now - _lastExecutionBeganAt;
        }

        private void UpdateStateAfterDoneOrFinished()
        {
            lock (_criticalSectionDoor)
            {
                var newState = GetStateToMoveToWhenDoneOrFinished();
                SetCurrentStateTo(newState);
            }
        }
        
        private CalmerState GetStateToMoveToWhenDoneOrFinished()
        {
            return DoneOrFinishedTransitions[_currentState];
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect SunHotels
Spain Spain
I Received a Bachelor's Degree in Computer Science at the Mathematics and Computer Science Faculty, University of Havana, Cuba.

I mainly work in web applications using C# and some Javascript. Some very few times do some Java.

Comments and Discussions