Delaying tasks

12/08/2013 08:53 U2_Caparzo#1
Well, sorry for post this here, but programming section is almost only deutsch.
I was tired of study and started programming a bit, and thought that might be a good idea to create this class (i know that something like this exists in project x, don't know exactly how did he code it)
this one is using two AutoResetEvents, one to notify when a new action was added and another one to force it's WaitOne() timeout, so basically the thread will work only when an action must be executed, i think that i'll have to lock the SortedList used to avoid a possible exception.

the question is, will be this better than a thread/timer with a const interval and checking all the actions that must be executed, and if there are others ways to do this.

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace CoreLib.Threading
{
    public class LazyTask
    {
        private static SortedList<DateTime, Action> delayedActions = new SortedList<DateTime, Action>();
        private static Thread thread = new Thread(Loop);
        private static AutoResetEvent synchronizer = new AutoResetEvent(false);
        private static object syncRoot = new object();
        private static bool started = false;

        private static void Loop()
        {
            while (true)
            {
                synchronizer.WaitOne();
                KeyValuePair<DateTime, Action> pair;
                lock (syncRoot)
                    pair = delayedActions.First();
                int millisecondsForNextInvoke = (int)Math.Max((pair.Key - DateTime.Now).Ticks / 10000, 0);
                synchronizer.WaitOne(millisecondsForNextInvoke);
                lock (syncRoot)
                {
                    if (DateTime.Now.Ticks >= pair.Key.Ticks)
                    {
                        pair.Value();
                        delayedActions.RemoveAt(0);
                    }
                    if (delayedActions.Count != 0)
                        synchronizer.Set();
                }
                Thread.Sleep(1);
            }
        }

        public static void AddTask(Action action, int initializeTime)
        {
            Start();
            lock (syncRoot)
                delayedActions.Add(DateTime.Now.AddMilliseconds(initializeTime), action);
            synchronizer.Set();
        }

        private static void Start()
        {
            if (!started)
            {
                started = true;
                thread.Start();
            }
        }
    }
}
tested and worked correctly, excuse me for my bad english again, i speak spanish, plus in chile it is 5:00 am and i'm a bit tired

Edit: A test i made some mins ago
Code:
using System;
using System.Diagnostics;
using CoreLib.Threading;

namespace ConsoleApplication1
{
    class Foo
    {
        public uint UId { get; set; }

        public void DelayedAction()
        {
            Console.WriteLine("writing UId: {0}, ms elapsed since program start: {1}", UId, Program.sw.ElapsedMilliseconds);
        }
    }
    class Program
    {
        public static Stopwatch sw;
        static void Main(string[] args)
        {
            Console.Title = "Test";
            Foo p1 = new Foo();
            p1.UId = 1;
            Foo p2 = new Foo();
            p2.UId = 2;
            Foo p3 = new Foo();
            p3.UId = 3;
            Foo p4 = new Foo();
            p4.UId = 4;


            //To avoid slower executions because of the jit
            sw = new Stopwatch();
            sw.Start();
            LazyTask.AddTask(p1.DelayedAction, 1000);
            //

            Console.ReadLine();
            Console.Clear();
            sw = new Stopwatch();
            sw.Start();
            LazyTask.AddTask(p1.DelayedAction, 10000);//10 seconds
            LazyTask.AddTask(p2.DelayedAction, 20000);//20 seconds
            LazyTask.AddTask(p3.DelayedAction, 15000);//15 seconds
            LazyTask.AddTask(p4.DelayedAction, 5000);// 5 seconds
            Console.ReadLine();
        }
    }
}
[Only registered and activated users can see links. Click Here To Register...]
12/08/2013 09:41 Super Aids#2
Make the delayedAction collection a threadsafe collection, because you may add actions to it from multiple threads at the same time.
12/08/2013 10:30 U2_Caparzo#3
Quote:
Originally Posted by Super Aids View Post
Make the delayedAction collection a threadsafe collection, because you may add actions to it from multiple threads at the same time.
yeah, i already have that detail in mind, i added the object for the lock but never added the lock itself :p
12/08/2013 11:16 Y u k i#4
Dont forget to sleep your while loop bro, additinally its possible you could make more use of a Stack or Queue instead of a list. Super Aids has a fair point aswell, check what works best for you. Generally speaking that way should be better than those timers :)
12/08/2013 19:43 U2_Caparzo#5
Quote:
Originally Posted by Y u k i View Post
Dont forget to sleep your while loop bro, additinally its possible you could make more use of a Stack or Queue instead of a list. Super Aids has a fair point aswell, check what works best for you. Generally speaking that way should be better than those timers :)
Hmm good point on the sleep, massive tasks could consume all the processor. I'm using a SortedDictionary because it will sort the collection based on the DateTime that the Action will be invoked, so the first item in the collection always will be the next action that must me invoked, Stacks and queues are unsorted collections
12/08/2013 20:24 _tao4229_#6
You can't use a regular stack or queue. What you need is a priority queue, which is what the SortedList/Dictionary emulates (well, almost. Priority queue implementations generally have O(1) peek, and balanced trees are O(log n)).

Edit for above: I lied, sorted lists are array backed (in increasing order, not heap order), so they do have O(1) peek. But O(n) insert according to MSDN.

The only problem with this approach is this:

I add an event to be processed in 6 minutes. The event loop peeks at this event, sees that the "first" event is to be processed in 6 minutes. Then it waits.

5 seconds from now, I queue up events to be processed in 10 seconds and another in 20 seconds.

When do those get executed?
12/08/2013 20:46 U2_Caparzo#7
Quote:
Originally Posted by _tao4229_ View Post
You can't use a regular stack or queue. What you need is a priority queue, which is what the SortedList/Dictionary emulates (well, almost. Priority queue implementations generally have O(1) peek, and balanced trees are O(log n)).

Edit for above: I lied, sorted lists are array backed (in increasing order, not heap order), so they do have O(1) peek. But O(n) insert according to MSDN.

The only problem with this approach is this:

I add an event to be processed in 6 minutes. The event loop peeks at this event, sees that the "first" event is to be processed in 6 minutes. Then it waits.

5 seconds from now, I queue up events to be processed in 10 seconds and another in 20 seconds.

When do those get executed?
that was my first problem, that is the reason behind using an autoresetevent instead Thread.Sleep()
Code:
                invokingSynchronizer.WaitOne(nextExecutionTime);//step 1
                if (DateTime.Now.Ticks >= pair.Key.Ticks)//step 2
                {
                    delayedActions.RemoveAt(0);
                    pair.Value();
                }
Lets say that an action must be executed in 6 minutes, the AutoResetEvent will be non-signaled, and the AutoResetEvent will make the thread wait it with a timeout of 6 minutes(step 1), but if a new action is added that must be executed in 10 seconds, it will set the AutoResetEvent to signaled, so the thread will pass inmediately to the step 2, that is why the if was added, if the time has passed it will execute the action, if not, it the cycle will start again, and the Timeout for the AutoResetEvent will be of 10 seconds because the new action will be the first element in the collection.

#Edit:
Reading the code rigth now, it shouldn't work, but it does :p, however now i'm seeing that there is one useless AutoResetEvent, one will do the job
12/09/2013 05:01 _DreadNought_#8
-Indirectly related-

It is nice to see something a little more "upto date" regarding threading around here :)