2 // System.Threading.Timer.cs
5 // Dick Porter (dick@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
9 // Copyright (C) 2004-2009 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System
.Runtime
.InteropServices
;
32 using System
.Collections
.Generic
;
33 using System
.Collections
;
34 using System
.Runtime
.CompilerServices
;
35 using System
.Threading
.Tasks
;
38 namespace System
.Threading
41 internal static class WasmRuntime
{
42 static Dictionary
<int, Action
> callbacks
;
45 [MethodImplAttribute(MethodImplOptions
.InternalCall
)]
46 static extern void SetTimeout (int timeout
, int id
);
48 internal static void ScheduleTimeout (int timeout
, Action action
) {
49 if (callbacks
== null)
50 callbacks
= new Dictionary
<int, Action
> ();
52 callbacks
[id
] = action
;
53 SetTimeout (timeout
, id
);
56 //XXX Keep this in sync with mini-wasm.c:mono_set_timeout_exec
57 static void TimeoutCallback (int id
) {
58 var cb
= callbacks
[id
];
59 callbacks
.Remove (id
);
67 public sealed class Timer
68 : MarshalByRefObject
, IDisposable
, IAsyncDisposable
70 static Scheduler scheduler
=> Scheduler
.Instance
;
71 #region Timer instance fields
72 TimerCallback callback
;
76 long next_run
; // in ticks. Only 'Scheduler' can change it except for new timers without due time.
78 bool is_dead
, is_added
;
80 public Timer (TimerCallback callback
, object state
, int dueTime
, int period
)
82 Init (callback
, state
, dueTime
, period
);
85 public Timer (TimerCallback callback
, object state
, long dueTime
, long period
)
87 Init (callback
, state
, dueTime
, period
);
90 public Timer (TimerCallback callback
, object state
, TimeSpan dueTime
, TimeSpan period
)
92 Init (callback
, state
, (long)dueTime
.TotalMilliseconds
, (long)period
.TotalMilliseconds
);
96 public Timer (TimerCallback callback
, object state
, uint dueTime
, uint period
)
98 // convert all values to long - with a special case for -1 / 0xffffffff
99 long d
= (dueTime
== UInt32
.MaxValue
) ? Timeout
.Infinite
: (long) dueTime
;
100 long p
= (period
== UInt32
.MaxValue
) ? Timeout
.Infinite
: (long) period
;
101 Init (callback
, state
, d
, p
);
104 public Timer (TimerCallback callback
)
106 Init (callback
, this, Timeout
.Infinite
, Timeout
.Infinite
);
109 void Init (TimerCallback callback
, object state
, long dueTime
, long period
)
111 if (callback
== null)
112 throw new ArgumentNullException ("callback");
114 this.callback
= callback
;
116 this.is_dead
= false;
117 this.is_added
= false;
119 Change (dueTime
, period
, true);
122 public bool Change (int dueTime
, int period
)
124 return Change (dueTime
, period
, false);
127 public bool Change (TimeSpan dueTime
, TimeSpan period
)
129 return Change ((long)dueTime
.TotalMilliseconds
, (long)period
.TotalMilliseconds
, false);
132 [CLSCompliant(false)]
133 public bool Change (uint dueTime
, uint period
)
135 // convert all values to long - with a special case for -1 / 0xffffffff
136 long d
= (dueTime
== UInt32
.MaxValue
) ? Timeout
.Infinite
: (long) dueTime
;
137 long p
= (period
== UInt32
.MaxValue
) ? Timeout
.Infinite
: (long) period
;
138 return Change (d
, p
, false);
141 public void Dispose ()
147 scheduler
.Remove (this);
150 public bool Change (long dueTime
, long period
)
152 return Change (dueTime
, period
, false);
155 const long MaxValue
= UInt32
.MaxValue
- 1;
157 bool Change (long dueTime
, long period
, bool first
)
159 if (dueTime
> MaxValue
)
160 throw new ArgumentOutOfRangeException ("dueTime", "Due time too large");
162 if (period
> MaxValue
)
163 throw new ArgumentOutOfRangeException ("period", "Period too large");
165 // Timeout.Infinite == -1, so this accept everything greater than -1
166 if (dueTime
< Timeout
.Infinite
)
167 throw new ArgumentOutOfRangeException ("dueTime");
169 if (period
< Timeout
.Infinite
)
170 throw new ArgumentOutOfRangeException ("period");
173 throw new ObjectDisposedException (null, Environment
.GetResourceString ("ObjectDisposed_Generic"));
175 due_time_ms
= dueTime
;
180 } else if (dueTime
< 0) { // Infinite == -1
182 /* No need to call Change () */
188 nr
= dueTime
* TimeSpan
.TicksPerMillisecond
+ GetTimeMonotonic ();
191 scheduler
.Change (this, nr
);
195 public bool Dispose (WaitHandle notifyObject
)
197 if (notifyObject
== null)
198 throw new ArgumentNullException ("notifyObject");
200 NativeEventCalls
.SetEvent (notifyObject
.SafeWaitHandle
);
204 public ValueTask
DisposeAsync ()
207 return new ValueTask (Task
.FromResult
<object> (null));
210 // extracted from ../../../../external/referencesource/mscorlib/system/threading/timer.cs
211 internal void KeepRootedWhileScheduled()
215 // TODO: Environment.TickCount should be enough as is everywhere else
216 [MethodImplAttribute(MethodImplOptions
.InternalCall
)]
217 static extern long GetTimeMonotonic ();
219 struct TimerComparer
: IComparer
, IComparer
<Timer
> {
220 int IComparer
.Compare (object x
, object y
)
224 Timer tx
= (x
as Timer
);
227 Timer ty
= (y
as Timer
);
230 return Compare(tx
, ty
);
233 public int Compare (Timer tx
, Timer ty
)
235 long result
= tx
.next_run
- ty
.next_run
;
236 return (int)Math
.Sign(result
);
240 sealed class Scheduler
{
241 static readonly Scheduler instance
= new Scheduler ();
243 volatile bool needReSort
= true;
245 long current_next_run
= Int64
.MaxValue
;
250 void InitScheduler () {
253 void WakeupScheduler () {
254 if (!scheduled_zero
) {
255 WasmRuntime
.ScheduleTimeout (0, this.RunScheduler
);
256 scheduled_zero
= true;
260 void RunScheduler() {
261 scheduled_zero
= false;
262 int ms_wait
= RunSchedulerLoop ();
264 WasmRuntime
.ScheduleTimeout (ms_wait
, this.RunScheduler
);
266 scheduled_zero
= true;
270 ManualResetEvent changed
;
272 void InitScheduler () {
273 changed
= new ManualResetEvent (false);
274 Thread thread
= new Thread (SchedulerThread
);
275 thread
.IsBackground
= true;
279 void WakeupScheduler () {
283 void SchedulerThread ()
285 Thread
.CurrentThread
.Name
= "Timer-Scheduler";
290 ms_wait
= RunSchedulerLoop ();
292 // Wait until due time or a timer is changed and moves from/to the first place in the list.
293 changed
.WaitOne (ms_wait
);
298 public static Scheduler Instance
{
299 get { return instance; }
304 list
= new List
<Timer
> (1024);
308 public void Remove (Timer timer
)
311 // If this is the next item due (index = 0), the scheduler will wake up and find nothing.
312 // No need to Pulse ()
313 InternalRemove (timer
);
317 public void Change (Timer timer
, long new_next_run
)
320 timer
.is_dead
= false;
326 if (!timer
.is_added
) {
327 timer
.next_run
= new_next_run
;
329 wake
= current_next_run
> new_next_run
;
331 if (new_next_run
== Int64
.MaxValue
) {
332 timer
.next_run
= new_next_run
;
333 InternalRemove (timer
);
337 if (!timer
.disposed
) {
338 // We should only change next_run after removing and before adding
339 timer
.next_run
= new_next_run
;
341 wake
= current_next_run
> new_next_run
;
349 // This should be the only caller to list.Add!
350 void Add (Timer timer
)
352 timer
.is_added
= true;
360 void InternalRemove (Timer timer
)
362 timer
.is_dead
= true;
366 static void TimerCB (object o
)
368 Timer timer
= (Timer
) o
;
369 timer
.callback (timer
.state
);
372 void FireTimer (Timer timer
) {
373 long period
= timer
.period_ms
;
374 long due_time
= timer
.due_time_ms
;
375 bool no_more
= (period
== -1 || ((period
== 0 || period
== Timeout
.Infinite
) && due_time
!= Timeout
.Infinite
));
377 timer
.next_run
= Int64
.MaxValue
;
378 timer
.is_dead
= true;
380 timer
.next_run
= GetTimeMonotonic () + TimeSpan
.TicksPerMillisecond
* timer
.period_ms
;
381 timer
.is_dead
= false;
383 ThreadPool
.UnsafeQueueUserWorkItem (TimerCB
, timer
);
386 int RunSchedulerLoop () {
389 long ticks
= GetTimeMonotonic ();
390 var comparer
= new TimerComparer();
397 long min_next_run
= Int64
.MaxValue
;
399 for (i
= 0; i
< list
.Count
; i
++) {
400 Timer timer
= list
[i
];
404 if (timer
.next_run
<= ticks
) {
408 min_next_run
= Math
.Min(min_next_run
, timer
.next_run
);
410 if ((timer
.next_run
> ticks
) && (timer
.next_run
< Int64
.MaxValue
))
411 timer
.is_dead
= false;
414 for (i
= 0; i
< list
.Count
; i
++) {
415 Timer timer
= list
[i
];
419 timer
.is_added
= false;
421 list
[i
] = list
[list
.Count
- 1];
423 list
.RemoveAt(list
.Count
- 1);
436 current_next_run
= min_next_run
;
437 if (min_next_run
!= Int64
.MaxValue
) {
438 long diff
= (min_next_run
- GetTimeMonotonic ()) / TimeSpan
.TicksPerMillisecond
;
439 if (diff
> Int32
.MaxValue
)
440 ms_wait
= Int32
.MaxValue
- 1;
442 ms_wait
= (int)(diff
);
453 Console.WriteLine ("BEGIN--");
454 for (int i = 0; i < list.Count; i++) {
455 Timer timer = (Timer) list.GetByIndex (i);
456 Console.WriteLine ("{0}: {1}", i, timer.next_run);
458 Console.WriteLine ("END----");