Compute lucene-style scores for our hits.
[beagle.git] / Util / Scheduler.cs
blobb03333269d30ba94a514ff8e170002b871c21ca0
1 //
2 // Scheduler.cs
3 //
4 // Copyright (C) 2004-2005 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.Text;
30 using System.Threading;
32 namespace Beagle.Util {
34 public class Scheduler {
36 static public bool Debug = false;
38 public enum Priority {
39 Shutdown = 0, // Do it on shutdown
40 Idle = 1, // Do it when the system is idle
41 Delayed = 2, // Do it soon
42 Immediate = 3, // Do it right now
45 public delegate void Hook ();
46 public delegate void TaskHook (Task task);
48 //////////////////////////////////////////////////////////////////////////////
50 public abstract class Task : IComparable {
52 private string tag = null;
53 private Priority priority = Priority.Delayed;
54 private int sub_priority = 0;
55 private DateTime trigger_time = DateTime.MinValue;
56 private DateTime timestamp; // when added to the scheduler
58 // Some metadata
59 public string Creator;
60 public string Description;
62 public ITaskCollector Collector = null;
63 public double Weight = 1.0;
65 public bool Reschedule = false;
67 private ArrayList task_groups = null;
68 private TaskGroupPrivate child_task_group = null;
70 ///////////////////////////////
73 // The tag is the task's unique identifier
74 public string Tag {
76 get { return tag; }
78 set {
79 // Don't allow us to change the tag of a scheduled task
80 if (tag == null || scheduler == null)
81 tag = value;
82 else
83 throw new Exception ("Can't change tag of " + tag + "!");
87 public Priority Priority {
89 get { return priority; }
91 set {
92 if (priority != value) {
93 priority = value;
94 Recompute ();
99 public int SubPriority {
101 get { return sub_priority; }
103 set {
104 if (sub_priority != value) {
105 sub_priority = value;
106 Recompute ();
111 public DateTime TriggerTime {
113 get { return trigger_time; }
115 set {
116 if (trigger_time != value) {
117 trigger_time = value;
118 Recompute ();
123 public DateTime Timestamp {
124 get { return timestamp; }
127 ///////////////////////////////
129 public void AddTaskGroup (TaskGroup group)
131 if (task_groups == null)
132 task_groups = new ArrayList ();
133 task_groups.Add (group);
136 private void IncrementAllTaskGroups ()
138 if (task_groups != null) {
139 foreach (TaskGroupPrivate group in task_groups) {
140 if (! group.Finished)
141 group.Increment ();
146 private void DecrementAllTaskGroups ()
148 if (task_groups != null) {
149 foreach (TaskGroupPrivate group in task_groups) {
150 if (! group.Finished)
151 group.Decrement ();
156 private void TouchAllTaskGroups ()
158 if (task_groups != null) {
159 foreach (TaskGroupPrivate group in task_groups) {
160 if (! group.Finished)
161 group.Touch ();
166 ///////////////////////////////
168 private Scheduler scheduler = null;
170 public void Schedule (Scheduler scheduler)
172 // Increment the task groups the first
173 // time a task is scheduled.
174 if (this.scheduler == null)
175 IncrementAllTaskGroups ();
176 this.timestamp = DateTime.Now;
177 this.scheduler = scheduler;
178 this.cancelled = false;
181 private void Recompute ()
183 if (scheduler != null)
184 Recompute ();
187 ///////////////////////////////
189 // A blocked task will not execute.
190 public bool Blocked {
191 get {
192 // Block the task if we have unexecuted children
193 return child_task_group != null && ! child_task_group.Finished;
197 ///////////////////////////////
199 private bool cancelled = false;
201 public bool Cancelled {
202 get { return cancelled; }
205 public void Cancel ()
207 if (! cancelled)
208 DecrementAllTaskGroups ();
209 cancelled = true;
212 ///////////////////////////////
214 // The Task's count keeps track of how many
215 // times it has been executed.
217 private int count = 0;
219 public int Count {
220 get { return count; }
223 ///////////////////////////////
225 public void SpawnChild (Task child_task)
227 if (child_task_group == null)
228 child_task_group = new TaskGroupPrivate ("Children of " + Tag, null, null);
229 child_task.AddTaskGroup (child_task_group);
230 scheduler.Add (child_task);
233 ///////////////////////////////
235 public void DoTask ()
237 if (! cancelled) {
238 if (Debug)
239 Logger.Log.Debug ("Starting task {0}", Tag);
240 child_task_group = null;
241 Reschedule = false;
242 TouchAllTaskGroups ();
244 Stopwatch sw = new Stopwatch ();
245 sw.Start ();
247 try {
248 DoTaskReal ();
249 } catch (Exception ex) {
250 Logger.Log.Warn ("Caught exception in DoTaskReal");
251 Logger.Log.Warn (" Tag: {0}", Tag);
252 Logger.Log.Warn (" Creator: {0}", Creator);
253 Logger.Log.Warn ("Description: {0}", Description);
254 Logger.Log.Warn (" Priority: {0} ({1})", Priority, SubPriority);
255 Logger.Log.Warn (ex);
257 sw.Stop ();
258 if (Debug)
259 Logger.Log.Debug ("Finished task {0} in {1}", Tag, sw);
261 if (Reschedule) {
262 ++count;
263 scheduler.Add (this); // re-add ourselves
264 } else {
265 DecrementAllTaskGroups ();
266 scheduler = null;
271 protected abstract void DoTaskReal ();
273 ///////////////////////////////
275 // Sort from lowest to highest priority
276 public int CompareTo (object obj)
278 Task other = obj as Task;
279 if (other == null)
280 return 1;
282 int cmp;
283 cmp = this.Priority.CompareTo (other.Priority);
284 if (cmp != 0)
285 return cmp;
287 cmp = this.SubPriority.CompareTo (other.SubPriority);
288 if (cmp != 0)
289 return cmp;
291 // Tasks that were added to the scheduler earlier take
292 // precedence over those that were added later.
293 cmp = DateTime.Compare (other.Timestamp, this.Timestamp);
294 if (cmp != 0)
295 return cmp;
297 // Try to break any ties
298 return this.GetHashCode () - other.GetHashCode ();
301 public override string ToString ()
303 StringBuilder sb = new StringBuilder ();
305 sb.AppendFormat ("{0} {1} ({2})\n", Priority, SubPriority, Timestamp);
307 sb.Append (Tag + "\n");
309 double t = (TriggerTime - DateTime.Now).TotalSeconds;
310 if (t > 0) {
311 if (t < 120)
312 sb.AppendFormat ("Hold for {0:0.00} seconds\n", t);
313 else
314 sb.AppendFormat ("Hold until {0}\n", TriggerTime);
317 if (Creator != null)
318 sb.AppendFormat ("Creator: {0}\n", Creator);
320 if (Description != null)
321 sb.Append (Description + "\n");
323 return sb.ToString ();
327 private class TaskHookWrapper : Task {
329 TaskHook hook;
331 public TaskHookWrapper (TaskHook hook)
333 this.hook = hook;
336 protected override void DoTaskReal ()
338 if (hook != null)
339 hook (this);
343 public static Task TaskFromHook (TaskHook hook)
345 return new TaskHookWrapper (hook);
348 //////////////////////////////////////////////////////////////////////////////
351 // Task Groups
354 public static TaskGroup NewTaskGroup (string name, Hook pre_hook, Hook post_hook)
356 return new TaskGroupPrivate (name, pre_hook, post_hook);
359 // The TaskGroup we hand back to the user is an interface that
360 // exposes minimal functionality.
361 public interface TaskGroup {
362 string Name { get; }
363 bool Finished { get; }
366 private class TaskGroupPrivate : TaskGroup {
367 private string name;
368 private int task_count = 0;
369 private bool touched = false;
370 private bool finished = false;
371 private Hook pre_hook;
372 private Hook post_hook;
374 public TaskGroupPrivate (string name,
375 Hook pre_hook,
376 Hook post_hook)
378 this.name = name;
379 this.pre_hook = pre_hook;
380 this.post_hook = post_hook;
383 public string Name {
384 get { return name; }
387 public bool Finished {
388 get { return finished; }
391 // Call this when a task is added to the task group.
392 public void Increment ()
394 if (finished)
395 throw new Exception ("Tried to increment a finished TaskGroup");
396 ++task_count;
399 // Call this when we execute a task in the task group.
400 public void Touch ()
402 if (finished)
403 throw new Exception ("Tried to touch a finished TaskGroup");
405 if (! touched) {
406 if (pre_hook != null) {
407 try {
408 pre_hook ();
409 } catch (Exception ex) {
410 Logger.Log.Warn ("Caught exception in pre_hook of task group '{0}'", Name);
411 Logger.Log.Warn (ex);
414 touched = true;
418 // Call this after a task in the task group is complete.
419 public void Decrement ()
421 if (finished)
422 throw new Exception ("Tried to decrement a finished TaskGroup");
424 --task_count;
425 // Only fire our post-hook if the pre-hook fired
426 // (or would have fired, had it been non-null)
427 if (task_count == 0 && touched) {
428 if (post_hook != null) {
429 try {
430 post_hook ();
431 } catch (Exception ex) {
432 Logger.Log.Warn ("Caught exception in post_hook of task group '{0}'", Name);
433 Logger.Log.Warn (ex);
436 finished = true;
441 //////////////////////////////////////////////////////////////////////////////
444 // Task Collector
446 // This is a mechanism for executing tasks in sets, possibly outside of
447 // priority order.
450 public interface ITaskCollector {
452 double GetMaximumWeight ();
454 void PreTaskHook ();
455 void PostTaskHook ();
458 //////////////////////////////////////////////////////////////////////////////
460 static private double global_delay = -1.0;
462 static Scheduler ()
464 string exercise;
465 exercise = Environment.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG");
467 if (exercise != null) {
468 if (exercise.Length > 2 && exercise [0] == 't')
469 global_delay = Double.Parse (exercise.Substring (1));
470 else
471 global_delay = 0.0;
475 //////////////////////////////////////////////////////////////////////////////
477 static private Scheduler global = new Scheduler ();
479 static public Scheduler Global {
480 get { return global; }
483 //////////////////////////////////////////////////////////////////////////////
485 private object big_lock = new object ();
487 // FIXME: shutdown tasks should probably be ordered by something
488 private Queue shutdown_task_queue = new Queue ();
490 private Hashtable tasks_by_tag = new Hashtable ();
491 private int executed_task_count = 0;
493 public void Add (Task task)
495 if (task == null)
496 return;
498 Task old_task = null;
500 lock (big_lock) {
502 // Keep track of when immediate priority tasks are
503 // added so that we can throttle if the scheduler
504 // is being slammed with them.
505 if (task.Priority == Priority.Immediate) {
506 // Shift our times down by one
507 Array.Copy (last_immediate_times, 1, last_immediate_times, 0, 4);
508 last_immediate_times [4] = DateTime.Now;
511 // Re-adding the same task is a no-op
512 old_task = tasks_by_tag [task.Tag] as Task;
513 if (old_task == task)
514 return;
516 if (Debug) {
517 Logger.Log.Debug ("Adding task");
518 Logger.Log.Debug ("Tag: {0}", task.Tag);
519 if (task.Description != null)
520 Logger.Log.Debug ("Desc: {0}", task.Description);
523 task.Schedule (this);
525 if (task.Priority == Priority.Shutdown)
526 shutdown_task_queue.Enqueue (task);
527 else
528 tasks_by_tag [task.Tag] = task;
530 Monitor.Pulse (big_lock);
533 // If we clobbered another task, call cancel on it.
534 // This happens after we release the lock, since
535 // cancellation could result in a task group post-hook
536 // being run.
537 if (old_task != null)
538 old_task.Cancel ();
541 public Task GetByTag (string tag)
543 lock (big_lock)
544 return tasks_by_tag [tag] as Task;
547 public bool ContainsByTag (string tag)
549 Task task = GetByTag (tag);
550 return task != null && !task.Cancelled;
553 public void Recompute ()
555 lock (big_lock)
556 Monitor.Pulse (big_lock);
559 //////////////////////////////////////////////////////////////////////////////
561 Thread thread = null;
562 public bool running = false;
564 public void Start ()
566 lock (this) {
567 if (thread != null)
568 return;
569 running = true;
570 thread = new Thread (new ThreadStart (Worker));
571 thread.Start ();
575 public void Stop ()
577 lock (big_lock) {
578 if (running) {
579 running = false;
580 Monitor.Pulse (big_lock);
586 // Delay Computations
588 // This code controls how we space out tasks
591 // FIXME: random magic constants
592 const double idle_threshold = 5.314159 * 60; // probably should be longer
593 const double idle_ramp_up_time = 5.271828 * 60; // probably should be longer
594 const double default_delayed_rate_factor = 9.03; // work about 1/10th of the time
595 const double default_idle_rate_factor = 2.097; // work about 1/3rd of the time
596 const double maximum_delay = 20; // never wait for more than 20s
597 const double min_throttled_delay = 1.5; // never wait less than this when throttled
599 DateTime[] last_immediate_times = new DateTime [5];
601 // The return value and duration_of_previous_task are both measured in seconds.
602 private double ComputeDelay (Priority priority_of_next_task,
603 double duration_of_previous_task)
605 if (global_delay >= 0.0)
606 return global_delay;
608 double rate_factor;
610 rate_factor = 2.0;
612 // Do everything faster the longer we are idle.
613 double idle_time = SystemInformation.InputIdleTime;
614 double idle_scale = 1.0;
615 bool is_idle = false;
616 bool need_throttle = false;
618 // Never speed up if we are using the battery.
619 if (idle_time > idle_threshold && ! SystemInformation.UsingBattery) {
620 is_idle = true;
621 double t = (idle_time - idle_threshold) / idle_ramp_up_time;
622 idle_scale = (1 - Math.Min (t, 1.0));
625 switch (priority_of_next_task) {
627 case Priority.Immediate:
628 rate_factor = 0;
630 if (last_immediate_times [0] != DateTime.MinValue) {
631 TimeSpan last_add_delta = DateTime.Now.Subtract (last_immediate_times [4]);
633 // If less than a second has gone by since the
634 // last immediate task was added, there is
635 // still a torrent of events coming in, and we
636 // may need to throttle.
637 if (last_add_delta.Seconds <= 1) {
638 TimeSpan between_add_delta = last_immediate_times [4].Subtract (last_immediate_times [0]);
640 // At least 5 immediate tasks have been
641 // added in the last second. We
642 // definitely need to throttle.
643 if (between_add_delta.Seconds <= 1) {
644 Logger.Log.Debug ("Thottling immediate priority tasks");
645 need_throttle = true;
646 rate_factor = idle_scale * default_idle_rate_factor;
651 break;
653 case Priority.Delayed:
654 rate_factor = idle_scale * default_delayed_rate_factor;
655 break;
657 case Priority.Idle:
658 rate_factor = idle_scale * default_idle_rate_factor;
659 break;
662 // FIXME: we should do something more sophisticated than this
663 // with the load average.
664 // Random numbers galore!
665 double load_average = SystemInformation.LoadAverageOneMinute;
666 if (load_average > 3.001)
667 rate_factor *= 5.002;
668 else if (load_average > 1.5003)
669 rate_factor *= 2.004;
671 double delay = rate_factor * duration_of_previous_task;
673 // space out delayed tasks a bit when we aren't idle
674 if (! is_idle
675 && priority_of_next_task == Priority.Delayed
676 && delay < 0.5)
677 delay = 0.5;
679 if (delay > maximum_delay)
680 delay = maximum_delay;
682 // If we need to throttle, make sure we don't delay less than
683 // a second and some.
684 if (need_throttle && delay < min_throttled_delay)
685 delay = min_throttled_delay;
687 return delay;
691 // The main loop
694 // A convenience function. There should be a
695 // constructor to TimeSpan that does this.
696 private static TimeSpan TimeSpanFromSeconds (double t)
698 // Wait barfs if you hand it a negative TimeSpan,
699 // so we are paranoid;
700 if (t < 0.001)
701 t = 0;
703 // 1 tick = 100 nanoseconds
704 long ticks = (long) (t * 1.0e+7);
705 return new TimeSpan (ticks);
708 private string status_str = null;
710 private void Worker ()
712 DateTime end_time_of_previous_task = DateTime.MinValue;
713 double duration_of_previous_task = 0.0;
715 Hook pre_hook = null;
716 Hook post_hook = null;
717 ArrayList to_be_executed = new ArrayList ();
719 while (running) {
721 status_str = "Finding next task to execute";
723 lock (big_lock) {
725 // If there are no pending tasks, wait
726 // on our lock and then re-start our
727 // while loop
728 if (tasks_by_tag.Count == 0) {
729 status_str = "Waiting on empty queue";
730 Monitor.Wait (big_lock);
731 continue;
734 // Walk across our list of tasks and find
735 // the next one to execute.
736 DateTime now = DateTime.Now;
737 DateTime next_trigger_time = DateTime.MaxValue;
738 Task next_task = null;
739 foreach (Task task in tasks_by_tag.Values) {
740 if (task.Blocked)
741 continue;
742 if (task.TriggerTime < now) {
743 if (next_task == null || next_task.CompareTo (task) < 0)
744 next_task = task;
745 } else if (task.TriggerTime < next_trigger_time)
746 next_trigger_time = task.TriggerTime;
749 // If we didn't find a task, wait for the next trigger-time
750 // and then re-start our while loop.
751 if (next_task == null) {
752 if (next_trigger_time == DateTime.MaxValue) {
753 status_str = "Waiting for an unblocked task";
754 Monitor.Wait (big_lock);
755 } else {
756 status_str = "Waiting for the next trigger time";
757 Monitor.Wait (big_lock, next_trigger_time - now);
759 continue;
762 // If we did find a task, do we want to execute it right now?
763 // Or should we wait a bit?
765 // How should we space things out?
766 double delay = 0;
767 delay = ComputeDelay (next_task.Priority, duration_of_previous_task);
768 delay = Math.Min (delay, (next_trigger_time - now).TotalSeconds);
770 // Adjust by the time that has actually elapsed since the
771 // last task.
772 delay -= (now - end_time_of_previous_task).TotalSeconds;
774 // If we still need to wait a bit longer, wait for the appropriate
775 // amount of time and then re-start our while loop.
776 if (delay > 0.001) {
777 status_str = "Waiting for next task.";
778 Monitor.Wait (big_lock, TimeSpanFromSeconds (delay));
779 continue;
783 // If we've made it to this point, it is time to start
784 // executing our selected task.
787 to_be_executed.Clear ();
789 if (next_task.Collector == null) {
791 to_be_executed.Add (next_task);
793 } else {
795 pre_hook = new Hook (next_task.Collector.PreTaskHook);
796 post_hook = new Hook (next_task.Collector.PostTaskHook);
798 // Find all eligible tasks with the same collector,
799 // and add them to the collection list.
800 now = DateTime.Now;
801 foreach (Task task in tasks_by_tag.Values)
802 if (task != next_task
803 && task.Collector == next_task.Collector
804 && !task.Blocked
805 && task.TriggerTime < now)
806 to_be_executed.Add (task);
808 // Order the tasks from highest to lowest priority.
809 // Our original task will always be the first item
810 // in the resulting array.
811 to_be_executed.Sort ();
812 to_be_executed.Add (next_task);
813 to_be_executed.Reverse ();
815 // Now find how many tasks can be executed before we
816 // exceed the collector's maximum weight. If necessary,
817 // prune the list of tasks.
818 double remaining_weight;
819 remaining_weight = next_task.Collector.GetMaximumWeight ();
820 int i = 0;
821 while (i < to_be_executed.Count && remaining_weight > 0) {
822 Task task;
823 task = to_be_executed [i] as Task;
824 remaining_weight -= task.Weight;
825 ++i;
827 if (i < to_be_executed.Count)
828 to_be_executed.RemoveRange (i, to_be_executed.Count - i);
831 // Remove the tasks we are about to execute from our
832 // master list.
833 foreach (Task task in to_be_executed)
834 tasks_by_tag.Remove (task.Tag);
836 // Pulse our lock, in case anyone is waiting for it.
837 Monitor.Pulse (big_lock);
840 // Now actually execute the set of tasks we found.
842 status_str = "Executing tasks";
844 DateTime start_time = DateTime.Now;
845 if (pre_hook != null) {
846 try {
847 pre_hook ();
848 } catch (Exception ex) {
849 Logger.Log.Error ("Caught exception in pre_hook '{0}'", pre_hook);
850 Logger.Log.Error (ex);
853 foreach (Task task in to_be_executed) {
854 task.DoTask ();
855 ++executed_task_count;
857 if (post_hook != null) {
858 try {
859 post_hook ();
860 } catch (Exception ex) {
861 Logger.Log.Error ("Caught exception in post_hook '{0}'", post_hook);
862 Logger.Log.Error (ex);
866 end_time_of_previous_task = DateTime.Now;
867 duration_of_previous_task = (end_time_of_previous_task - start_time).TotalSeconds;
870 // Execute all shutdown tasks
871 foreach (Task task in shutdown_task_queue)
872 if (! task.Cancelled && ! task.Blocked)
873 task.DoTask ();
875 if (Debug)
876 Logger.Log.Debug ("Scheduler.Worker finished");
879 //////////////////////////////////////////////////////////////////////////////
881 public string GetHumanReadableStatus ()
883 StringBuilder sb = new StringBuilder ();
885 lock (big_lock) {
887 ArrayList blocked_tasks = new ArrayList ();
888 ArrayList future_tasks = new ArrayList ();
889 ArrayList pending_tasks = new ArrayList ();
891 DateTime now = DateTime.Now;
892 foreach (Task task in tasks_by_tag.Values) {
893 if (task.Blocked)
894 blocked_tasks.Add (task);
895 else if (task.TriggerTime > now)
896 future_tasks.Add (task);
897 else
898 pending_tasks.Add (task);
901 blocked_tasks.Sort ();
902 blocked_tasks.Reverse ();
904 future_tasks.Sort ();
905 future_tasks.Reverse ();
907 pending_tasks.Sort ();
908 pending_tasks.Reverse ();
910 sb.Append ("Scheduler:\n");
911 sb.AppendFormat ("Count: {0}\n", executed_task_count);
913 if (status_str != null)
914 sb.AppendFormat ("Status: {0}\n", status_str);
916 int pos = 1;
917 sb.Append ("\nPending Tasks:\n");
918 foreach (Task task in pending_tasks) {
919 sb.AppendFormat ("{0} {1}\n", pos, task);
920 ++pos;
922 if (pos == 1)
923 sb.Append ("Scheduler queue is empty.\n");
926 if (future_tasks.Count > 0) {
927 sb.Append ("\nFuture Tasks:\n");
928 foreach (Task task in future_tasks)
929 sb.AppendFormat ("{0}\n", task);
932 if (blocked_tasks.Count > 0) {
933 sb.Append ("\nBlocked Tasks:\n");
934 foreach (Task task in blocked_tasks)
935 sb.AppendFormat ("{0}\n", task);
939 sb.Append ('\n');
940 return sb.ToString ();
944 #if false
945 class TestTask : Scheduler.Task {
947 private class TestCollector : Scheduler.ITaskCollector {
949 public double GetMinimumWeight ()
951 return 0;
954 public double GetMaximumWeight ()
956 return 5;
959 public void PreTaskHook ()
961 Console.WriteLine ("+++ Pre-Task Hook");
964 public void PostTaskHook ()
966 Console.WriteLine ("+++ Post-Task Hook");
970 protected override void DoTaskReal ()
972 Console.WriteLine ("Doing task '{0}' at {1}", Tag, DateTime.Now);
973 Thread.Sleep (200);
974 if (Tag == "Bar")
975 Reschedule = true;
978 static void BeginTaskGroup ()
980 Console.WriteLine ("--- Begin Task Group!");
983 static void EndTaskGroup ()
985 Console.WriteLine ("--- End Task Group!");
988 static void Main ()
990 Scheduler sched = Scheduler.Global;
992 Scheduler.TaskGroup tg = Scheduler.NewTaskGroup ("foo",
993 new Scheduler.Hook (BeginTaskGroup),
994 new Scheduler.Hook (EndTaskGroup));
996 sched.Start ();
998 Scheduler.Task task;
1000 task = new TestTask ();
1001 task.Tag = "Foo";
1002 task.AddTaskGroup (tg);
1003 task.Priority = Scheduler.Priority.Delayed;
1004 task.TriggerTime = DateTime.Now.AddSeconds (7);
1005 sched.Add (task);
1007 task = new TestTask ();
1008 task.Tag = "Bar";
1009 task.AddTaskGroup (tg);
1010 task.Priority = Scheduler.Priority.Delayed;
1011 sched.Add (task);
1013 Scheduler.ITaskCollector collector = null;
1014 for (int i = 0; i < 20; ++i) {
1015 if ((i % 10) == 0)
1016 collector = new TestCollector ();
1017 task = new TestTask ();
1018 task.Tag = String.Format ("Baboon {0}", i);
1019 task.Collector = collector;
1020 task.Priority = Scheduler.Priority.Delayed;
1021 sched.Add (task);
1024 while (true) {
1025 Thread.Sleep (1000);
1029 #endif