1 // TODO:
2 //	DispatcherObject returned by BeginInvoke must allow:
3 //	   * Waiting until delegate is invoked.
4 //	   See: BeginInvoke documentation for details
5 //
6 //	Implement the "Invoke" methods, they are currently not working.
7 //
8 //	Add support for disabling the dispatcher and resuming it.
9 //	Add support for Waiting for new tasks to be pushed, so that we dont busy loop.
10 //	Add support for aborting an operation (emit the hook.operationaborted too)
11 //
12 // Very confusing information about Shutdown: it states that shutdown is
13 // not over, until all events are unwinded, and also states that all events
14 // are aborted at that point.  See 'Dispatcher.InvokeShutdown' docs,
15 //
16 // Testing reveals that
17 //	 -> InvokeShutdown() stops processing, even if events are available,
18 //		there is no "unwinding" of events, even of higher priority events,
19 //		they are just ignored.
20 //
21 // The documentation for the Dispatcher family is poorly written, complete
22 // sections are cut-and-pasted that add no value and the important pieces
23 // like (what is a frame) is not on the APIs, but scattered everywhere else
24 //
25 // -----------------------------------------------------------------------
26 // Permission is hereby granted, free of charge, to any person obtaining
27 // a copy of this software and associated documentation files (the
28 // "Software"), to deal in the Software without restriction, including
29 // without limitation the rights to use, copy, modify, merge, publish,
30 // distribute, sublicense, and/or sell copies of the Software, and to
31 // permit persons to whom the Software is furnished to do so, subject to
32 // the following conditions:
33 //
34 // The above copyright notice and this permission notice shall be
35 // included in all copies or substantial portions of the Software.
36 //
37 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
41 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
42 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
43 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 //
45 // Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
46 // Copyright (c) 2016 Quamotion (http://quamotion.mobi)
47 //
48 // Authors:
49 //	Miguel de Icaza (miguel@novell.com)
50 //	Frederik Carlier (frederik.carlier@quamotion.mobi)
51 //
52 using System;
53 using System.Collections;
54 using System.Collections.Generic;
55 using System.ComponentModel;
56 using System.Security;
57 using System.Threading;
58 
59 namespace System.Windows.Threading {
60 
61 	[Flags]
62 	internal enum Flags {
63 		ShutdownStarted = 1,
64 		Shutdown = 2,
65 		Disabled = 4
66 	}
67 
68 	public sealed class Dispatcher {
69 		static Dictionary<Thread, Dispatcher> dispatchers = new Dictionary<Thread, Dispatcher> ();
70 		static object olock = new object ();
71 		static DispatcherFrame main_execution_frame = new DispatcherFrame ();
72 
73 		const int TOP_PRIO = (int)DispatcherPriority.Send;
74 		Thread base_thread;
75 		PokableQueue [] priority_queues = new PokableQueue [TOP_PRIO+1];
76 
77 		Flags flags;
78 		int queue_bits;
79 
80 		//
81 		// Used to notify the dispatcher thread that new data is available
82 		//
83 		EventWaitHandle wait;
84 
85 		//
86 		// The hooks for this Dispatcher
87 		//
88 		DispatcherHooks hooks;
89 
90 		//
91 		// The current DispatcherFrame active in a given Dispatcher, we use this to
92 		// keep a linked list of all active frames, so we can "ExitAll" frames when
93 		// requested
94 		DispatcherFrame current_frame;
95 
Dispatcher(Thread t)96 		Dispatcher (Thread t)
97 		{
98 			base_thread = t;
99 			for (int i = 1; i <= (int) DispatcherPriority.Send; i++)
100 				priority_queues [i] = new PokableQueue ();
101 			wait = new EventWaitHandle (false, EventResetMode.AutoReset);
102 			hooks = new DispatcherHooks (this);
103 		}
104 
105 		[EditorBrowsable (EditorBrowsableState.Never)]
CheckAccess()106 		public bool CheckAccess ()
107 		{
108 			return Thread.CurrentThread == base_thread;
109 		}
110 
111 		[EditorBrowsable (EditorBrowsableState.Never)]
VerifyAccess()112 		public void VerifyAccess ()
113 		{
114 			if (Thread.CurrentThread != base_thread)
115 				throw new InvalidOperationException ("Invoked from a different thread");
116 		}
117 
ValidatePriority(DispatcherPriority priority, string parameterName)118 		public static void ValidatePriority (DispatcherPriority priority, string parameterName)
119 		{
120 			if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
121 				throw new InvalidEnumArgumentException (parameterName);
122 		}
123 
BeginInvoke(Delegate method, params object[] args)124 		public DispatcherOperation BeginInvoke (Delegate method, params object[] args)
125 		{
126 			throw new NotImplementedException ();
127 		}
128 
BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args)129 		public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, params object[] args)
130 		{
131 			throw new NotImplementedException ();
132 		}
133 
134 		[Browsable (false)]
135 		[EditorBrowsable (EditorBrowsableState.Never)]
BeginInvoke(DispatcherPriority priority, Delegate method)136 		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method)
137 		{
138 			if (priority < 0 || priority > DispatcherPriority.Send)
139 				throw new InvalidEnumArgumentException ("priority");
140 			if (priority == DispatcherPriority.Inactive)
141 				throw new ArgumentException ("priority can not be inactive", "priority");
142 			if (method == null)
143 				throw new ArgumentNullException ("method");
144 
145 			DispatcherOperation op = new DispatcherOperation (this, priority, method);
146 			Queue (priority, op);
147 
148 			return op;
149 		}
150 
151 		[Browsable (false)]
152 		[EditorBrowsable (EditorBrowsableState.Never)]
BeginInvoke(DispatcherPriority priority, Delegate method, object arg)153 		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg)
154 		{
155 			if (priority < 0 || priority > DispatcherPriority.Send)
156 				throw new InvalidEnumArgumentException ("priority");
157 			if (priority == DispatcherPriority.Inactive)
158 				throw new ArgumentException ("priority can not be inactive", "priority");
159 			if (method == null)
160 				throw new ArgumentNullException ("method");
161 
162 			DispatcherOperation op = new DispatcherOperation (this, priority, method, arg);
163 
164 			Queue (priority, op);
165 
166 			return op;
167 		}
168 
169 		[Browsable (false)]
170 		[EditorBrowsable (EditorBrowsableState.Never)]
BeginInvoke(DispatcherPriority priority, Delegate method, object arg, params object [] args)171 		public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
172 		{
173 			if (priority < 0 || priority > DispatcherPriority.Send)
174 				throw new InvalidEnumArgumentException ("priority");
175 			if (priority == DispatcherPriority.Inactive)
176 				throw new ArgumentException ("priority can not be inactive", "priority");
177 			if (method == null)
178 				throw new ArgumentNullException ("method");
179 
180 			DispatcherOperation op = new DispatcherOperation (this, priority, method, arg, args);
181 			Queue (priority, op);
182 
183 			return op;
184 		}
185 
InvokeAsync(Action callback)186 		public DispatcherOperation InvokeAsync (Action callback)
187 		{
188 			return this.BeginInvoke(callback);
189 		}
190 
InvokeAsync(Action callback, DispatcherPriority priority)191 		public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority)
192 		{
193 			return this.BeginInvoke(callback, priority);
194 		}
195 
InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)196 		public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
197 		{
198 			return this.BeginInvoke(callback, priority);
199 		}
200 
Invoke(Delegate method, params object[] args)201 		public object Invoke (Delegate method, params object[] args)
202 		{
203 			throw new NotImplementedException ();
204 		}
205 
Invoke(Delegate method, TimeSpan timeout, params object[] args)206 		public object Invoke (Delegate method, TimeSpan timeout, params object[] args)
207 		{
208 			throw new NotImplementedException ();
209 		}
210 
Invoke(Delegate method, TimeSpan timeout, DispatcherPriority priority, params object[] args)211 		public object Invoke (Delegate method, TimeSpan timeout, DispatcherPriority priority, params object[] args)
212 		{
213 			throw new NotImplementedException ();
214 		}
215 
Invoke(Delegate method, DispatcherPriority priority, params object[] args)216 		public object Invoke (Delegate method, DispatcherPriority priority, params object[] args)
217 		{
218 			throw new NotImplementedException ();
219 		}
220 
221 		[Browsable (false)]
222 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, Delegate method)223 		public object Invoke (DispatcherPriority priority, Delegate method)
224 		{
225 			if (priority < 0 || priority > DispatcherPriority.Send)
226 				throw new InvalidEnumArgumentException ("priority");
227 			if (priority == DispatcherPriority.Inactive)
228 				throw new ArgumentException ("priority can not be inactive", "priority");
229 			if (method == null)
230 				throw new ArgumentNullException ("method");
231 
232 			DispatcherOperation op = new DispatcherOperation (this, priority, method);
233 			Queue (priority, op);
234 			PushFrame (new DispatcherFrame ());
235 
236 			throw new NotImplementedException ();
237 		}
238 
239 		[Browsable (false)]
240 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, Delegate method, object arg)241 		public object Invoke (DispatcherPriority priority, Delegate method, object arg)
242 		{
243 			if (priority < 0 || priority > DispatcherPriority.Send)
244 				throw new InvalidEnumArgumentException ("priority");
245 			if (priority == DispatcherPriority.Inactive)
246 				throw new ArgumentException ("priority can not be inactive", "priority");
247 			if (method == null)
248 				throw new ArgumentNullException ("method");
249 
250 			Queue (priority, new DispatcherOperation (this, priority, method, arg));
251 			throw new NotImplementedException ();
252 		}
253 
254 		[Browsable (false)]
255 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, Delegate method, object arg, params object [] args)256 		public object Invoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
257 		{
258 			if (priority < 0 || priority > DispatcherPriority.Send)
259 				throw new InvalidEnumArgumentException ("priority");
260 			if (priority == DispatcherPriority.Inactive)
261 				throw new ArgumentException ("priority can not be inactive", "priority");
262 			if (method == null)
263 				throw new ArgumentNullException ("method");
264 
265 			Queue (priority, new DispatcherOperation (this, priority, method, arg, args));
266 
267 			throw new NotImplementedException ();
268 		}
269 
270 		[Browsable (false)]
271 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, TimeSpan timeout, Delegate method)272 		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method)
273 		{
274 			throw new NotImplementedException ();
275 		}
276 
277 		[Browsable (false)]
278 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)279 		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)
280 		{
281 			throw new NotImplementedException ();
282 		}
283 
284 		[Browsable (false)]
285 		[EditorBrowsable (EditorBrowsableState.Never)]
Invoke(DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)286 		public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)
287 		{
288 			throw new NotImplementedException ();
289 		}
290 
Queue(DispatcherPriority priority, DispatcherOperation x)291 		void Queue (DispatcherPriority priority, DispatcherOperation x)
292 		{
293 			int p = ((int) priority);
294 			PokableQueue q = priority_queues [p];
295 
296 			lock (q){
297 				int flag = 1 << p;
298 				q.Enqueue (x);
299 				queue_bits |= flag;
300 			}
301 			hooks.EmitOperationPosted (x);
302 
303 			if (Thread.CurrentThread != base_thread)
304 				wait.Set ();
305 		}
306 
Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority)307 		internal void Reprioritize (DispatcherOperation op, DispatcherPriority oldpriority)
308 		{
309 			int oldp = (int) oldpriority;
310 			PokableQueue q = priority_queues [oldp];
311 
312 			lock (q){
313 				q.Remove (op);
314 			}
315 			Queue (op.Priority, op);
316 			hooks.EmitOperationPriorityChanged (op);
317 		}
318 
319 		public static Dispatcher CurrentDispatcher {
320 			get {
321 				lock (olock){
322 					Thread t = Thread.CurrentThread;
323 					Dispatcher dis = FromThread (t);
324 
325 					if (dis != null)
326 						return dis;
327 
328 					dis = new Dispatcher (t);
329 					dispatchers [t] = dis;
330 					return dis;
331 				}
332 			}
333 		}
334 
FromThread(Thread thread)335 		public static Dispatcher FromThread (Thread thread)
336 		{
337 			Dispatcher dis;
338 
339 			if (dispatchers.TryGetValue (thread, out dis))
340 				return dis;
341 
342 			return null;
343 		}
344 
345 		public Thread Thread {
346 			get {
347 				return base_thread;
348 			}
349 		}
350 
351 		[SecurityCritical]
Run()352 		public static void Run ()
353 		{
354 			// Set Continue, because the previous run could clean
355 			// this flag by Dispatcher.ExitAllFrames.
356 			main_execution_frame.Continue = true;
357 			PushFrame (main_execution_frame);
358 		}
359 
360 		[SecurityCritical]
PushFrame(DispatcherFrame frame)361 		public static void PushFrame (DispatcherFrame frame)
362 		{
363 			if (frame == null)
364 				throw new ArgumentNullException ("frame");
365 
366 			Dispatcher dis = CurrentDispatcher;
367 
368 			if (dis.HasShutdownFinished)
369 				throw new InvalidOperationException ("The Dispatcher has shut down");
370 			if (frame.dispatcher != null)
371 				throw new InvalidOperationException ("Frame is already running on a different dispatcher");
372 			if ((dis.flags & Flags.Disabled) != 0)
373 				throw new InvalidOperationException ("Dispatcher processing has been disabled");
374 
375 			frame.ParentFrame = dis.current_frame;
376 			dis.current_frame = frame;
377 
378 			frame.dispatcher = dis;
379 
380 			dis.RunFrame (frame);
381 
382 			frame.dispatcher = null;
383 			dis.current_frame = frame.ParentFrame;
384 			frame.ParentFrame = null;
385 		}
386 
PerformShutdown()387 		void PerformShutdown ()
388 		{
389 			EventHandler h;
390 
391 			h = ShutdownStarted;
392 			if (h != null)
393 				h (this, new EventArgs ());
394 
395 			flags |= Flags.Shutdown;
396 
397 			h = ShutdownFinished;
398 			if (h != null)
399 				h (this, new EventArgs ());
400 
401 			priority_queues = null;
402 			wait = null;
403 		}
404 
RunFrame(DispatcherFrame frame)405 		void RunFrame (DispatcherFrame frame)
406 		{
407 			while (frame.Continue) {
408 				while (queue_bits != 0){
409 					for (int i = TOP_PRIO; i > 0 && queue_bits != 0; i--){
410 						int current_bit = queue_bits & (1 << i);
411 						if (current_bit != 0){
412 							PokableQueue q = priority_queues [i];
413 
414 							do {
415 								DispatcherOperation task;
416 
417 								lock (q){
418 									// if we are done with this queue, leave.
419 									if (q.Count == 0){
420 										queue_bits &= ~current_bit;
421 										break;
422 									}
423 									task = (DispatcherOperation) q.Dequeue ();
424 								}
425 
426 								task.Invoke ();
427 
428 								//
429 								// call hooks.
430 								//
431 								if (task.Status == DispatcherOperationStatus.Aborted)
432 									hooks.EmitOperationAborted (task);
433 								else
434 									hooks.EmitOperationCompleted (task);
435 
436 								if (!frame.Continue)
437 									return;
438 
439 								if (HasShutdownStarted){
440 									PerformShutdown ();
441 									return;
442 								}
443 
444 								//
445 								// If a higher-priority task comes in, go do that
446 								//
447 								if (current_bit < (queue_bits & ~current_bit))
448 								{
449 									i = TOP_PRIO + 1; // for-loop decreases by one
450 									break;
451 								}
452 							} while (true);
453 						}
454 					}
455 				}
456 				hooks.EmitInactive ();
457 
458 				wait.WaitOne ();
459 				wait.Reset ();
460 			}
461 		}
462 
463 		[EditorBrowsable (EditorBrowsableState.Advanced)]
464 		public DispatcherHooks Hooks {
465 			[SecurityCritical]
466 			get { throw new NotImplementedException (); }
467 		}
468 
469 		public bool HasShutdownStarted {
470 			get {
471 				return (flags & Flags.ShutdownStarted) != 0;
472 			}
473 		}
474 
475 		public bool HasShutdownFinished {
476 			get {
477 				return (flags & Flags.Shutdown) != 0;
478 			}
479 		}
480 
481 		//
482 		// Do no work here, so that any events are thrown on the owning thread
483 		//
484 		[SecurityCritical]
InvokeShutdown()485 		public void InvokeShutdown ()
486 		{
487 			flags |= Flags.ShutdownStarted;
488 		}
489 
490 		[SecurityCritical]
BeginInvokeShutdown(DispatcherPriority priority)491 		public void BeginInvokeShutdown (DispatcherPriority priority)
492 		{
493 			throw new NotImplementedException ();
494 		}
495 
496 		[SecurityCritical]
ExitAllFrames()497 		public static void ExitAllFrames ()
498 		{
499 			Dispatcher dis = CurrentDispatcher;
500 
501 			for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
502 				if (frame.exit_on_request)
503 					frame.Continue = false;
504 				else {
505 					//
506 					// Stop unwinding the frames at the first frame that is
507 					// long running
508 					break;
509 				}
510 			}
511 		}
512 
DisableProcessing()513 		public DispatcherProcessingDisabled DisableProcessing ()
514 		{
515 			throw new NotImplementedException ();
516 		}
517 
518 		public event EventHandler ShutdownStarted;
519 		public event EventHandler ShutdownFinished;
520 		public event DispatcherUnhandledExceptionEventHandler UnhandledException;
521 		public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
522 	}
523 
524 	internal class PokableQueue {
525 		const int initial_capacity = 32;
526 
527 		int size, head, tail;
528 		object [] array;
529 
PokableQueue(int capacity)530 		internal PokableQueue (int capacity)
531 		{
532 			array = new object [capacity];
533 		}
534 
PokableQueue()535 		internal PokableQueue () : this (initial_capacity)
536 		{
537 		}
538 
Enqueue(object obj)539 		public void Enqueue (object obj)
540 		{
541 			if (size == array.Length)
542 					Grow ();
543 			array[tail] = obj;
544 			tail = (tail+1) % array.Length;
545 			size++;
546 		}
547 
Dequeue()548 		public object Dequeue ()
549 		{
550 			if (size < 1)
551 					throw new InvalidOperationException ();
552 			object result = array[head];
553 			array [head] = null;
554 			head = (head + 1) % array.Length;
555 			size--;
556 			return result;
557 		}
558 
Grow()559 		void Grow ()
560 		{
561 			int newc = array.Length * 2;
562 			object[] new_contents = new object[newc];
563 			array.CopyTo (new_contents, 0);
564 			array = new_contents;
565 			head = 0;
566 			tail = head + size;
567 		}
568 
569 		public int Count {
570 			get {
571 				return size;
572 			}
573 		}
574 
Remove(object obj)575 		public void Remove (object obj)
576 		{
577 			for (int i = 0; i < size; i++){
578 				if (array [(head+i) % array.Length] == obj){
579 					for (int j = i; j < size-i; j++)
580 						array [(head +j) % array.Length] = array [(head+j+1) % array.Length];
581 					size--;
582 					if (size < 0)
583 						size = array.Length-1;
584 					tail--;
585 				}
586 			}
587 		}
588 	}
589 }
590