C#|.NET : Generic Concurrent Queue (6/6)

... by g corallo, on Flickr
, a photo by g corallo on Flickr.

Visit these posts for Part 1, Part 2, Part 3, Part 4, Part 5, and real life implementation

In this concluding post we will make some changes to further separate some concerns and see multiple queues in action. First we will take the queue initialization to higher level in availability and define the queues in our top level class, i.e. App.xaml.cs. This way our queues are available everywhere in the application. We will move out background enqueue-ing to class level and expose it through a  method. This is to encapsulate the behavior to self-enqueue-ing in the class itself. So the user of the class does not have to know anything about the queue, they could simply call the class’ method and the class will queue itself for background work. Let’s start.
First, let’s modify the code of TypeOneTask class we created in Part 5, and encapsulate enqueue behavior in the class itself. We will introduce a new public method QueueInForBackgroundWork:

        public void QueueInForBackgroundWork()
        {
            Debug.WriteLine("UI Thread : {0} enqueue starting", this.Name);
            App.t1Queue.Process(this, true, false);
            Debug.WriteLine("UI Thread : {0} enqueue finished", this.Name);
        }

Before going to consumption code for this method in our main page’s code behind, let’s first set-up TypeTwoTask class and a new queue for the same. We will create a new class file TaskTwoQueue.cs and write code in this file. Most of the code in these classes is similar to what we created in Part5, so I will write all the code in one code block:

namespace TaskQueue
{
    public class TaskTwoQueue : ConcurrentQueue<TypeTwoTask>
    {
        #region queue implementationsc
        TypeTwoTaskEqualityComparer _itemEqualityComparer = new TypeTwoTaskEqualityComparer();
        public override bool IsQueueAlive
        {
            get
            {
                return base.IsQueueAlive;
            }
        }
        public override IEqualityComparer<TypeTwoTask> QueueItemEualityComparer
        {
            get
            {
                return _itemEqualityComparer;
            }
        }
        public override bool IsQueueAvailableForEnqueue()
        {
            if (this.Count > 10)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        protected override void ProcessRequest(TypeTwoTask taskRequest, bool Async)
        {
            taskRequest.Process();
        }
        public override void Exit(bool discardPendingRequests)
        {
            base.Exit(discardPendingRequests);
        }
        public override void DoWhenQueueGetsEmpty()
        {
            //doing nothing
        }
        #endregion
    }

    public class TypeTwoTask
    {
        Random testWait = new Random();
        public string Name { get; set; }
        public event EventHandler BackgroundProcessFinished;
        public TypeTwoTask(string name)
        {
            Name = name;
        }
        public void Process()
        {
            DateTime _st = DateTime.Now;
            Debug.WriteLine("BG Thread : Task {0} starting @ {1}", Name, _st.ToString("hh:mm:ss.fff"));
            Thread.CurrentThread.Join(testWait.Next(735, 1923));
            Debug.WriteLine("BG Thread : Task {0} Ended @ {1}", Name, DateTime.Now.ToString("hh:mm:ss.fff"));
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                if (BackgroundProcessFinished != null)
                {
                    BackgroundProcessFinished(this, new EventArgs());
                }
            });
        }
        public void QueueInForBackgroundWork()
        {
            Debug.WriteLine("UI Thread : {0} enqueue starting", this.Name);
            App.t2Queue.Process(this, true, false);
            Debug.WriteLine("UI Thread : {0} enqueue finished", this.Name);
        }
    }

    public class TypeTwoTaskEqualityComparer : IEqualityComparer<TypeTwoTask>
    {
        public bool Equals(TypeTwoTask A, TypeTwoTask B)
        {
            if (A != null && B != null)
            {
                return A.Name == B.Name;
            }
            else
            {
                return false;
            }
        }
        public int GetHashCode(TypeTwoTask itm)
        {
            return itm.GetHashCode();
        }
    }

}

We are ready with our second queue. We will introduce this queue in our application at the application level. In app.xaml.cs we will define both the queues like so (mind that both the queues are static):

    public partial class App : Application
    {
        public static TaskOneQueue t1Queue = new TaskOneQueue();
        public static TaskTwoQueue t2Queue = new TaskTwoQueue();
    }

The final user of our two classes, the main page, now, does not need to know about the queue (for previous code see part 5). It will only define the fields of appropriate task type, and call its QueuInForBackgroundWork as and when required. To demonstrate this behavior, we will modify MainPage.xaml.cs. We will first define 3 tasks of both the types at the page level. We will then have event handlers for both the types of the tasks to have informed about the completion of the task on UI thread. Button_click enqueues TypeOneTask in the queue by calling QueueInForBackgroundWork method and buttonDummy1_Click does the same for TypeTwoTask. Here is the code:

    public partial class MainPage : PhoneApplicationPage
    {
        TypeOneTask _t1 = new TypeOneTask("Type1 Task-1");
        TypeOneTask _t2 = new TypeOneTask("Type1 Task-2");
        TypeOneTask _t3 = new TypeOneTask("Type1 Task-3");

        TypeTwoTask _t4 = new TypeTwoTask("Type2 Task-4");
        TypeTwoTask _t5 = new TypeTwoTask("Type2 Task-5");
        TypeTwoTask _t6 = new TypeTwoTask("Type2 Task-6");

        public MainPage()
        {
            InitializeComponent();
            this.BackKeyPress += new EventHandler<System.ComponentModel.CancelEventArgs>(MainPage_BackKeyPress);
            _t2.BackgroundProcessFinished += (object sender, EventArgs e) => { Debug.WriteLine("UI Thread : TypeOneTask _t1 finished!"); };
            _t3.BackgroundProcessFinished += (object sender, EventArgs e) => { Debug.WriteLine("UI Thread : TypeOneTask _t2 finished!"); };
            _t5.BackgroundProcessFinished += (object sender, EventArgs e) => { Debug.WriteLine("UI Thread : TypeTwoTask _t5 finished!"); };
            _t6.BackgroundProcessFinished += (object sender, EventArgs e) => { Debug.WriteLine("UI Thread : TypeTwoTask _t6 finished!"); };
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _t1.QueueInForBackgroundWork();
            _t2.QueueInForBackgroundWork();
            _t3.QueueInForBackgroundWork();
        }
        private void buttonDummy1_Click(object sender, RoutedEventArgs e)
        {
            _t4.QueueInForBackgroundWork();
            _t5.QueueInForBackgroundWork();
            _t6.QueueInForBackgroundWork();
        }

        private void buttonDummy2_Click(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("UI Thread : Dummy button 2 clicked!");
        }

    }

When you run the the app in debug mode, you get three buttons – Button1, dummy 1, and dummy 2, same as we had in part 5. This time Button1 and dummy 1 both enqueue tasks and dummy 2 is just to demonstrate that UI thread is free. Now, with 1-2 seconds delay, click on these three buttons top to bottom. You will get following log in your Debug output:

Output.Debug

UI Thread : Type1 Task-1 enqueue starting
UI Thread : Type1 Task-1 enqueue finished
UI Thread : Type1 Task-2 enqueue starting
UI Thread : Type1 Task-2 enqueue finished
UI Thread : Type1 Task-3 enqueue starting
UI Thread : Type1 Task-3 enqueue finished
BG Thread : Task Type1 Task-1 starting @ 12:29:35.530
UI Thread : Type2 Task-4 enqueue starting
UI Thread : Type2 Task-4 enqueue finished
BG Thread : Task Type2 Task-4 starting @ 12:29:36.502
UI Thread : Type2 Task-5 enqueue starting
UI Thread : Type2 Task-5 enqueue finished
UI Thread : Type2 Task-6 enqueue starting
UI Thread : Type2 Task-6 enqueue finished
BG Thread : Task Type1 Task-1 Ended @ 12:29:37.403
BG Thread : Task Type1 Task-2 starting @ 12:29:37.413
BG Thread : Task Type2 Task-4 Ended @ 12:29:37.575
BG Thread : Task Type2 Task-5 starting @ 12:29:37.581
UI Thread : Dummy button 2 clicked!
BG Thread : Task Type1 Task-2 Ended @ 12:29:38.500
BG Thread : Task Type1 Task-3 starting @ 12:29:38.506
UI Thread : TypeOneTask _t1 finished!
BG Thread : Task Type2 Task-5 Ended @ 12:29:38.635
BG Thread : Task Type2 Task-6 starting @ 12:29:38.642
UI Thread : TypeTwoTask _t5 finished!
BG Thread : Task Type1 Task-3 Ended @ 12:29:39.576
UI Thread : TypeOneTask _t2 finished!
BG Thread : Task Type2 Task-6 Ended @ 12:29:39.691
UI Thread : TypeTwoTask _t6 finished!

The analysis of this output shows that Type1 tasks were enqueued first, because I clicked Button1 first, and as soon as all three tasks are enqueued UI thread was freed. Also, “Task Type1 Task-1” started its BG work. Because UI was free, after two seconds, I clicked Dummy 1 button which enqueued all the three tasks of TypeTwoTask in its queue. Both these queues are working on their own background threads. UI thread is completely free. When tasks get over UI gets a message.

To summarize, Generic Concurrent Queue has following characteristics:

  • Generic and abstract, to be inherited to implement for any type.
  • Process requests can be queued up for BG thread.
  • Has a single lazy-init BG thread.
  • Supports sync and async requests.
  • By supplying a sync process request, you can stop other processes in queue and have sync request process first. Queue resumes automatically after the sync task is over.
  • Queue can be paused and restarted as and when required.
  • Queue can be exited | killed as and when required.
  • Inherited class defines rules for Queue capacity.
  • Inherited class defines rules for process request duplicate value.
  • Uses basic lock/wait/pulse mechanism, so compatible with almost all variants of .NET

Please feel free to comment. Hope you find this concurrent queue useful.

Advertisements

2 thoughts on “C#|.NET : Generic Concurrent Queue (6/6)

  1. Pingback: C#|.NET : Generic Concurrent Queue (5/6) | Sharp Statements

  2. Pingback: Dew Drop – February 27, 2014 (#1732) | Morning Dew

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s