4 // Copyright (C) 2004 Novell, Inc.
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.
28 using System
.Collections
;
30 using System
.Threading
;
32 namespace Beagle
.Util
{
34 public class Scheduler
{
36 static public bool Debug
= false;
38 static private double global_delay
= -1.0;
39 static private bool immediate_priority_only
= false;
40 static private string exercise_the_dog
= null;
44 // We used to support the EXERCISE_THE_DOG env variable, but
46 exercise_the_dog
= Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG");
47 if (exercise_the_dog
== null &&
48 Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER") != null)
49 exercise_the_dog
= "harder fallback";
51 if (exercise_the_dog
!= null) {
55 if (exercise_the_dog
.Length
> 2 && exercise_the_dog
[0] == 't')
56 global_delay
= Double
.Parse (exercise_the_dog
.Substring (1));
59 if (Environment
.GetEnvironmentVariable ("BEAGLE_IMMEDIATE_PRIORITY_ONLY") != null)
60 immediate_priority_only
= true;
63 //////////////////////////////////////////////////////////////////////////////
65 static private Scheduler
global = new Scheduler ();
67 static public Scheduler Global
{
68 get { return global; }
71 //////////////////////////////////////////////////////////////////////////////
73 public enum Priority
{
74 Shutdown
= 0, // Do it on shutdown
75 Idle
= 1, // Do it when the system is idle
76 Generator
= 2, // Do it soon, but not *too* soon
77 Delayed
= 3, // Do it soon
78 Immediate
= 4, // Do it right now
81 public delegate void Hook ();
82 public delegate void TaskHook (Task task
);
84 //////////////////////////////////////////////////////////////////////////////
86 public abstract class Task
: IComparable
{
88 // A unique identifier
92 public string Creator
;
93 public string Description
;
95 public Priority Priority
= Priority
.Idle
;
96 public int SubPriority
= 0;
98 public DateTime Timestamp
;
99 public DateTime TriggerTime
= DateTime
.MinValue
;
101 public ITaskCollector Collector
= null;
102 public double Weight
= 1.0;
104 public bool Reschedule
= false;
106 ///////////////////////////////
108 private ArrayList task_groups
= null;
110 public void AddTaskGroup (TaskGroup
group)
112 if (task_groups
== null)
113 task_groups
= new ArrayList ();
114 task_groups
.Add (group);
117 private void IncrementAllTaskGroups ()
119 if (task_groups
!= null) {
120 foreach (TaskGroupPrivate
group in task_groups
) {
121 if (! group.Finished
)
127 private void DecrementAllTaskGroups ()
129 if (task_groups
!= null) {
130 foreach (TaskGroupPrivate
group in task_groups
) {
131 if (! group.Finished
)
137 private void TouchAllTaskGroups ()
139 if (task_groups
!= null) {
140 foreach (TaskGroupPrivate
group in task_groups
) {
141 if (! group.Finished
)
147 ///////////////////////////////
149 private Scheduler scheduler
= null;
151 public Scheduler ThisScheduler
{
152 get { return scheduler; }
155 public void Schedule (Scheduler scheduler
)
157 // Increment the task groups the first
158 // time a task is scheduled.
159 if (this.scheduler
== null)
160 IncrementAllTaskGroups ();
161 this.scheduler
= scheduler
;
164 ///////////////////////////////
166 private bool cancelled
= false;
168 public bool Cancelled
{
169 get { return cancelled; }
172 public void Cancel ()
175 DecrementAllTaskGroups ();
179 ///////////////////////////////
181 // The Task's count keeps track of how many
182 // times it has been executed.
184 private int count
= 0;
187 get { return count; }
190 ///////////////////////////////
192 public void DoTask ()
195 TouchAllTaskGroups ();
198 Logger
.Log
.Debug ("Starting task {0}", Tag
);
199 Stopwatch sw
= new Stopwatch ();
204 Logger
.Log
.Debug ("Finished task {0} in {1}", Tag
, sw
);
205 } catch (Exception ex
) {
206 Logger
.Log
.Warn ("Caught exception in DoTaskReal");
207 Logger
.Log
.Warn (" Tag: {0}", Tag
);
208 Logger
.Log
.Warn (" Creator: {0}", Creator
);
209 Logger
.Log
.Warn ("Description: {0}", Description
);
210 Logger
.Log
.Warn (" Priority: {0} ({1})", Priority
, SubPriority
);
211 Logger
.Log
.Warn (ex
);
216 ThisScheduler
.Add (this);
218 DecrementAllTaskGroups ();
223 protected abstract void DoTaskReal ();
225 ///////////////////////////////
227 // Sort from lowest to highest priority
228 public int CompareTo (object obj
)
230 Task other
= obj
as Task
;
235 cmp
= this.Priority
.CompareTo (other
.Priority
);
239 cmp
= this.SubPriority
.CompareTo (other
.SubPriority
);
243 cmp
= other
.Timestamp
.CompareTo (this.Timestamp
);
247 // Try to break any ties
248 return this.GetHashCode ().CompareTo (other
.GetHashCode ());
251 public override string ToString ()
253 StringBuilder sb
= new StringBuilder ();
255 sb
.AppendFormat ("{0} {1}\n", Priority
, SubPriority
);
257 sb
.Append (Tag
+ "\n");
259 double t
= (TriggerTime
- DateTime
.Now
).TotalSeconds
;
262 sb
.AppendFormat ("Trigger in {0:0.00} seconds\n", t
);
264 sb
.AppendFormat ("Trigger at {0}\n", TriggerTime
);
268 sb
.AppendFormat ("Creator: {0}\n", Creator
);
270 if (Description
!= null)
271 sb
.Append (Description
+ "\n");
273 return sb
.ToString ();
277 private class TaskHookWrapper
: Task
{
281 public TaskHookWrapper (TaskHook hook
)
286 protected override void DoTaskReal ()
293 public static Task
TaskFromHook (TaskHook hook
)
295 return new TaskHookWrapper (hook
);
298 //////////////////////////////////////////////////////////////////////////////
304 public static TaskGroup
NewTaskGroup (string name
, Hook pre_hook
, Hook post_hook
)
306 return new TaskGroupPrivate (name
, pre_hook
, post_hook
);
309 // We split the task group data structure into two parts:
310 // TaskGroup and TaskGroupPrivate. The TaskGroup we hand
311 // back to the user exposes minimal functionality.
312 public abstract class TaskGroup
{
315 protected TaskGroup (string name
) {
323 public abstract bool Finished { get; }
326 private class TaskGroupPrivate
: TaskGroup
{
327 private int task_count
= 0;
328 private bool touched
= false;
329 private bool finished
= false;
330 private Hook pre_hook
;
331 private Hook post_hook
;
333 public TaskGroupPrivate (string name
,
335 Hook post_hook
) : base (name
)
337 this.pre_hook
= pre_hook
;
338 this.post_hook
= post_hook
;
341 public override bool Finished
{
342 get { return finished; }
345 public void Increment ()
348 throw new Exception ("Tried to increment a finished TaskGroup");
355 throw new Exception ("Tried to touch a finished TaskGroup");
358 if (pre_hook
!= null) {
361 } catch (Exception ex
) {
362 Logger
.Log
.Warn ("Caught exception in pre_hook of task group '{0}'", Name
);
363 Logger
.Log
.Warn (ex
);
370 public void Decrement ()
373 throw new Exception ("Tried to decrement a finished TaskGroup");
376 // Only fire our post-hook if the pre-hook fired
377 // (or would have fired, had it been non-null)
378 if (task_count
== 0 && touched
) {
379 if (post_hook
!= null) {
382 } catch (Exception ex
) {
383 Logger
.Log
.Warn ("Caught exception in post_hook of task group '{0}'", Name
);
384 Logger
.Log
.Warn (ex
);
392 //////////////////////////////////////////////////////////////////////////////
397 // This is a mechanism for executing tasks in sets, possibly outside of
401 public interface ITaskCollector
{
403 double GetMinimumWeight ();
404 double GetMaximumWeight ();
407 void PostTaskHook ();
410 //////////////////////////////////////////////////////////////////////////////
412 // FIXME: shutdown tasks should probably be ordered by something
413 private Queue shutdown_task_queue
= new Queue ();
415 private ArrayList task_queue
= new ArrayList ();
416 private Hashtable task_by_tag
= new Hashtable ();
417 private int executed_task_count
= 0;
419 public enum AddType
{
421 OptionallyReplaceExisting
,
423 BlockUntilNoCollision
// This is very dangerous!
427 public bool Add (Task task
, AddType add_type
)
429 Task old_task
= null;
434 if (immediate_priority_only
&& task
.Priority
!= Priority
.Immediate
)
437 // Keep track of when immediate priority tasks are
438 // added so that we can throttle if the scheduler
439 // is being slammed with them.
440 if (task
.Priority
== Priority
.Immediate
) {
441 // Shift our times down by one
442 Array
.Copy (last_immediate_times
, 1, last_immediate_times
, 0, 4);
443 last_immediate_times
[4] = DateTime
.Now
;
446 old_task
= task_by_tag
[task
.Tag
] as Task
;
447 if (old_task
== task
)
450 // Wait until there isn't anything in the task queue with this
451 // tag. This is EXTREMELY DANGEROUS. It could easily
452 // block for a long time, and might lead to weird deadlocks
453 // if used without the utmost of care.
454 // FIXME: If we are blocking until a task is executed, we should
455 // probably allow it to skip ahead in the queue.
456 if (add_type
== AddType
.BlockUntilNoCollision
) {
457 while (old_task
!= null) {
458 Monitor
.Wait (task_queue
);
461 old_task
= task_by_tag
[task
.Tag
] as Task
;
465 if (add_type
== AddType
.DeferToExisting
469 if (add_type
== AddType
.OnlyReplaceExisting
474 Logger
.Log
.Debug ("Adding task");
475 Logger
.Log
.Debug ("Tag: {0}", task
.Tag
);
476 if (task
.Description
!= null)
477 Logger
.Log
.Debug ("Desc: {0}", task
.Description
);
480 task
.Timestamp
= DateTime
.Now
;
481 task
.Schedule (this);
483 if (task
.Priority
== Priority
.Shutdown
) {
484 shutdown_task_queue
.Enqueue (task
);
486 int i
= task_queue
.BinarySearch (task
);
489 task_queue
.Insert (i
, task
);
490 task_by_tag
[task
.Tag
] = task
;
494 Monitor
.Pulse (task_queue
);
497 if (old_task
!= null)
503 public bool Add (Task task
)
505 return Add (task
, AddType
.OptionallyReplaceExisting
);
508 public Task
GetByTag (string tag
)
511 return task_by_tag
[tag
] as Task
;
515 public bool ContainsByTag (string tag
)
517 Task task
= GetByTag (tag
);
518 return task
!= null && !task
.Cancelled
;
523 //////////////////////////////////////////////////////////////////////////////
525 private string status_str
= null;
526 private DateTime next_task_time
;
528 public string GetHumanReadableStatus ()
530 StringBuilder sb
= new StringBuilder ();
532 sb
.Append ("Scheduler:\n");
534 sb
.Append (String
.Format ("Count: {0}\n", executed_task_count
));
536 if (next_task_time
.Ticks
> 0)
537 sb
.Append (String
.Format ("Next task in {0:0.00} seconds\n",
538 (next_task_time
- DateTime
.Now
).TotalSeconds
));
540 if (status_str
!= null)
541 sb
.Append ("Status: " + status_str
+ "\n");
545 for (int i
= task_queue
.Count
- 1; i
>= 0; --i
) {
546 Task task
= task_queue
[i
] as Task
;
547 if (task
== null || task
.Cancelled
)
550 sb
.AppendFormat ("{0} ", pos
);
551 sb
.Append (task
.ToString ());
558 sb
.Append ("Scheduler queue is empty.\n");
563 return sb
.ToString ();
566 //////////////////////////////////////////////////////////////////////////////
568 Thread thread
= null;
569 public bool running
= false;
577 thread
= new Thread (new ThreadStart (Worker
));
588 Monitor
.Pulse (task_queue
);
594 // Delay Computations
596 // This code controls how we space out tasks
599 // FIXME: random magic constants
600 const double idle_threshold
= 5.314159 * 60; // probably should be longer
601 const double idle_ramp_up_time
= 5.271828 * 60; // probably should be longer
602 const double default_delayed_rate_factor
= 9.03; // work about 1/10th of the time
603 const double default_idle_rate_factor
= 2.097; // work about 1/3rd of the time
604 const double default_maximum_delay
= 20; // never wait for more than 20s
606 DateTime
[] last_immediate_times
= new DateTime
[5];
608 private double GetIdleTime ()
610 return SystemInformation
.InputIdleTime
;
613 // The return value and duration_of_previous_task are both measured in seconds.
614 private double ComputeDelay (Priority priority_of_next_task
,
615 double duration_of_previous_task
)
617 if (global_delay
>= 0.0)
624 // Do everything faster the longer we are idle.
625 double idle_time
= GetIdleTime ();
626 double idle_scale
= 1.0;
627 bool is_idle
= false;
628 bool need_throttle
= false;
630 // Never speed up if we are using the battery.
631 if (idle_time
> idle_threshold
&& ! SystemInformation
.UsingBattery
) {
633 double t
= (idle_time
- idle_threshold
) / idle_ramp_up_time
;
634 idle_scale
= (1 - Math
.Min (t
, 1.0));
637 switch (priority_of_next_task
) {
639 case Priority
.Immediate
:
642 if (last_immediate_times
[0] != DateTime
.MinValue
) {
643 TimeSpan last_add_delta
= DateTime
.Now
.Subtract (last_immediate_times
[4]);
645 // If less than a second has gone by since the
646 // last immediate task was added, there is
647 // still a torrent of events coming in, and we
648 // may need to throttle.
649 if (last_add_delta
.Seconds
<= 1) {
650 TimeSpan between_add_delta
= last_immediate_times
[4].Subtract (last_immediate_times
[0]);
652 // At least 5 immediate tasks have been
653 // added in the last second. We
654 // definitely need to throttle.
655 if (between_add_delta
.Seconds
<= 1) {
656 Logger
.Log
.Debug ("Thottling immediate priority tasks");
657 need_throttle
= true;
658 rate_factor
= idle_scale
* default_idle_rate_factor
;
665 case Priority
.Generator
:
666 case Priority
.Delayed
:
667 rate_factor
= idle_scale
* default_delayed_rate_factor
;
671 rate_factor
= idle_scale
* default_idle_rate_factor
;
675 // FIXME: we should do something more sophisticated than this
676 // with the load average.
677 // Random numbers galore!
678 double load_average
= SystemInformation
.LoadAverageOneMinute
;
679 if (load_average
> 3.001)
680 rate_factor
*= 5.002;
681 else if (load_average
> 1.5003)
682 rate_factor
*= 2.004;
684 double delay
= rate_factor
* duration_of_previous_task
;
686 // space out delayed tasks a bit when we aren't idle
688 && priority_of_next_task
== Priority
.Delayed
692 if (delay
> default_maximum_delay
)
693 delay
= default_maximum_delay
;
695 // If we need to throttle, make sure we don't delay less than
696 // a second and some.
697 if (need_throttle
&& delay
< 1.25)
707 // A convenience function. There should be a
708 // constructor to TimeSpan that does this.
709 private static TimeSpan
TimeSpanFromSeconds (double t
)
711 // Wait barfs if you hand it a negative TimeSpan,
712 // so we are paranoid;
716 // 1 tick = 100 nanoseconds
717 long ticks
= (long) (t
* 1.0e+7);
718 return new TimeSpan (ticks
);
722 private void DescribeTaskQueue (string note
, int i0
, int i1
)
724 Console
.WriteLine ("----------------------");
725 Console
.WriteLine (note
);
726 for (int i
=i0
; i
<i1
; ++i
) {
727 Task t
= task_queue
[i
] as Task
;
731 else if (t
.Cancelled
)
732 xxx
= t
.Tag
+ " CANCELLED";
735 Console
.WriteLine ("{0}: {1}", i
, xxx
);
737 Console
.WriteLine ("----------------------");
741 // Remove nulls and cancelled tasks from the queue.
742 // Note: this does no locking!
743 private void CleanQueue ()
745 int i
= task_queue
.Count
- 1;
747 Task t
= task_queue
[i
] as Task
;
751 // Remove cancelled items from the tag hash
752 task_by_tag
.Remove (t
.Tag
);
756 if (i
< task_queue
.Count
- 1)
757 task_queue
.RemoveRange (i
+1, task_queue
.Count
- 1 - i
);
760 private void Worker ()
762 DateTime time_of_last_task
= DateTime
.MinValue
;
763 double duration_of_last_task
= 1;
765 Hook pre_hook
= null;
766 Hook post_hook
= null;
767 ArrayList collection
= new ArrayList ();
777 // First, remove any null or cancelled tasks
778 // we find in the task_queue.
781 // If the task queue is now empty, wait on our lock
782 // and then re-start our while loop
783 if (task_queue
.Count
== 0) {
784 next_task_time
= new DateTime ();
785 status_str
= "Waiting on empty queue";
786 Monitor
.Wait (task_queue
);
787 status_str
= "Working";
791 // Find the next event that is past it's trigger time.
792 i
= task_queue
.Count
- 1;
793 DateTime now
= DateTime
.Now
;
794 DateTime next_trigger_time
= DateTime
.MaxValue
;
797 Task t
= task_queue
[i
] as Task
;
798 if (t
!= null && ! t
.Cancelled
) {
799 if (t
.TriggerTime
< now
) {
801 task_i
= i
; // Remember the task's position in the queue.
804 // Keep track of when the next possible trigger time is.
805 if (t
.TriggerTime
< next_trigger_time
)
806 next_trigger_time
= t
.TriggerTime
;
812 // If we didn't find a task, wait for the next trigger-time
813 // and then re-start our while loop.
815 next_task_time
= next_trigger_time
;
816 status_str
= "Waiting for next trigger time.";
817 Monitor
.Wait (task_queue
, next_trigger_time
- now
);
818 next_task_time
= new DateTime ();
819 status_str
= "Working";
823 // If we did find a task, do we want to execute it right now?
824 // Or should we wait a bit?
826 // How should we space things out?
828 delay
= ComputeDelay (task
.Priority
, duration_of_last_task
);
829 delay
= Math
.Min (delay
, (next_trigger_time
- DateTime
.Now
).TotalSeconds
);
831 // Adjust by the time that has actually elapsed since the
833 delay
-= (DateTime
.Now
- time_of_last_task
).TotalSeconds
;
835 // If we still need to wait a bit longer, wait for the appropriate
836 // amount of time and then re-start our while loop.
838 next_task_time
= DateTime
.Now
.AddSeconds (delay
);
839 status_str
= "Waiting for next task.";
840 // Never wait more than 15 seconds.
841 Monitor
.Wait (task_queue
, TimeSpanFromSeconds (Math
.Min (delay
, 15)));
842 next_task_time
= new DateTime ();
843 status_str
= "Working";
847 // Remove this task from the queue
848 task_queue
[task_i
] = null;
849 task_by_tag
.Remove (task
.Tag
);
851 if (task
.Collector
== null) {
855 collection
.Add (task
);
861 pre_hook
= new Hook (task
.Collector
.PreTaskHook
);
862 post_hook
= new Hook (task
.Collector
.PostTaskHook
);
864 double weight
= task
.Weight
;
865 double min_weight
= task
.Collector
.GetMinimumWeight ();
866 double max_weight
= task
.Collector
.GetMaximumWeight ();
868 collection
.Add (task
);
870 // We left i pointing at task
872 while (i
>= 0 && weight
< max_weight
) {
873 Task t
= task_queue
[i
] as Task
;
876 && t
.Collector
== task
.Collector
877 && t
.TriggerTime
< now
) {
879 // Only include differently-prioritized tasks
880 // in the same collection if the total weight so far
881 // is below the minimum.
882 if (t
.Priority
!= task
.Priority
&& weight
> min_weight
)
886 if (weight
> max_weight
)
891 // Remove the task from the queue and clean
892 // up the by-tag hash table.
893 task_queue
[i
] = null;
894 task_by_tag
.Remove (t
.Tag
);
900 // Clean the queue again
901 // (We need to do this to keep rescheduled tasks from blocking
902 // stuff from getting cleaned off the end of the queue)
905 Monitor
.Pulse (task_queue
);
909 // If we actually found tasks we like, do them now.
910 if (collection
.Count
> 0) {
911 DateTime t1
= DateTime
.Now
;
912 if (pre_hook
!= null) {
915 } catch (Exception ex
) {
916 Logger
.Log
.Error ("Caught exception in pre_hook '{0}'", pre_hook
);
917 Logger
.Log
.Error (ex
);
920 foreach (Task task
in collection
) {
922 ++executed_task_count
;
924 if (post_hook
!= null) {
927 } catch (Exception ex
) {
928 Logger
.Log
.Error ("Caught exception in post_hook '{0}'", post_hook
);
929 Logger
.Log
.Error (ex
);
932 DateTime t2
= DateTime
.Now
;
934 duration_of_last_task
= (t2
- t1
).TotalSeconds
;
935 time_of_last_task
= t2
;
943 // Execute all shutdown tasks
944 while (shutdown_task_queue
.Count
> 0) {
945 Task t
= shutdown_task_queue
.Dequeue () as Task
;
946 if (t
!= null && ! t
.Cancelled
&& t
.Priority
== Priority
.Shutdown
) {
948 // FIXME: Support Pre/Post task hooks
950 } catch (Exception ex
) {
951 Logger
.Log
.Error ("Caught exception while performing shutdown tasks in the scheduler");
952 Logger
.Log
.Error (ex
);
958 Logger
.Log
.Debug ("Scheduler.Worker finished");
963 class TestTask
: Scheduler
.Task
{
965 private class TestCollector
: Scheduler
.ITaskCollector
{
967 public double GetMinimumWeight ()
972 public double GetMaximumWeight ()
977 public void PreTaskHook ()
979 Console
.WriteLine ("+++ Pre-Task Hook");
982 public void PostTaskHook ()
984 Console
.WriteLine ("+++ Post-Task Hook");
988 protected override void DoTaskReal ()
990 Console
.WriteLine ("Doing task '{0}' at {1}", Tag
, DateTime
.Now
);
996 static void BeginTaskGroup ()
998 Console
.WriteLine ("--- Begin Task Group!");
1001 static void EndTaskGroup ()
1003 Console
.WriteLine ("--- End Task Group!");
1008 Scheduler sched
= Scheduler
.Global
;
1010 Scheduler
.TaskGroup tg
= Scheduler
.NewTaskGroup ("foo",
1011 new Scheduler
.Hook (BeginTaskGroup
),
1012 new Scheduler
.Hook (EndTaskGroup
));
1016 Scheduler
.Task task
;
1018 task
= new TestTask ();
1020 task
.AddTaskGroup (tg
);
1021 task
.Priority
= Scheduler
.Priority
.Delayed
;
1022 task
.TriggerTime
= DateTime
.Now
.AddSeconds (7);
1025 task
= new TestTask ();
1027 task
.AddTaskGroup (tg
);
1028 task
.Priority
= Scheduler
.Priority
.Delayed
;
1031 Scheduler
.ITaskCollector collector
= null;
1032 for (int i
= 0; i
< 20; ++i
) {
1034 collector
= new TestCollector ();
1035 task
= new TestTask ();
1036 task
.Tag
= String
.Format ("Baboon {0}", i
);
1037 task
.Collector
= collector
;
1038 task
.Priority
= Scheduler
.Priority
.Delayed
;
1043 Thread
.Sleep (1000);