2010-04-06 Jb Evain <jbevain@novell.com>
[mcs.git] / class / WindowsBase / System.Windows.Threading / Dispatcher.cs
blob6d17a28d8e3244bdf24288190317c4b269b111bc
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)
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,
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.
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
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.
45 // Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
47 // Authors:
48 // Miguel de Icaza (miguel@novell.com)
50 using System;
51 using System.Collections;
52 using System.Collections.Generic;
53 using System.ComponentModel;
54 using System.Security;
55 using System.Threading;
57 namespace System.Windows.Threading {
59 [Flags]
60 internal enum Flags {
61 ShutdownStarted = 1,
62 Shutdown = 2,
63 Disabled = 4
66 public sealed class Dispatcher {
67 static Dictionary<Thread, Dispatcher> dispatchers = new Dictionary<Thread, Dispatcher> ();
68 static object olock = new object ();
69 static DispatcherFrame main_execution_frame = new DispatcherFrame ();
71 const int TOP_PRIO = (int)DispatcherPriority.Send;
72 Thread base_thread;
73 PokableQueue [] priority_queues = new PokableQueue [TOP_PRIO+1];
75 Flags flags;
76 int queue_bits;
79 // Used to notify the dispatcher thread that new data is available
81 EventWaitHandle wait;
84 // The hooks for this Dispatcher
86 DispatcherHooks hooks;
89 // The current DispatcherFrame active in a given Dispatcher, we use this to
90 // keep a linked list of all active frames, so we can "ExitAll" frames when
91 // requested
92 DispatcherFrame current_frame;
94 Dispatcher (Thread t)
96 base_thread = t;
97 for (int i = 1; i <= (int) DispatcherPriority.Send; i++)
98 priority_queues [i] = new PokableQueue ();
99 wait = new EventWaitHandle (false, EventResetMode.AutoReset);
100 hooks = new DispatcherHooks (this);
103 [EditorBrowsable (EditorBrowsableState.Never)]
104 public bool CheckAccess ()
106 return Thread.CurrentThread == base_thread;
109 [EditorBrowsable (EditorBrowsableState.Never)]
110 public void VerifyAccess ()
112 if (Thread.CurrentThread != base_thread)
113 throw new InvalidOperationException ("Invoked from a different thread");
116 public static void ValidatePriority (DispatcherPriority priority, string parameterName)
118 if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
119 throw new InvalidEnumArgumentException (parameterName);
122 public DispatcherOperation BeginInvoke (Delegate method, object[] args)
124 throw new NotImplementedException ();
127 public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, object[] args)
129 throw new NotImplementedException ();
132 [Browsable (false)]
133 [EditorBrowsable (EditorBrowsableState.Never)]
134 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method)
136 if (priority < 0 || priority > DispatcherPriority.Send)
137 throw new InvalidEnumArgumentException ("priority");
138 if (priority == DispatcherPriority.Inactive)
139 throw new ArgumentException ("priority can not be inactive", "priority");
140 if (method == null)
141 throw new ArgumentNullException ("method");
143 DispatcherOperation op = new DispatcherOperation (this, priority, method);
144 Queue (priority, op);
146 return op;
149 [Browsable (false)]
150 [EditorBrowsable (EditorBrowsableState.Never)]
151 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg)
153 if (priority < 0 || priority > DispatcherPriority.Send)
154 throw new InvalidEnumArgumentException ("priority");
155 if (priority == DispatcherPriority.Inactive)
156 throw new ArgumentException ("priority can not be inactive", "priority");
157 if (method == null)
158 throw new ArgumentNullException ("method");
160 DispatcherOperation op = new DispatcherOperation (this, priority, method, arg);
162 Queue (priority, op);
164 return op;
167 [Browsable (false)]
168 [EditorBrowsable (EditorBrowsableState.Never)]
169 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
171 if (priority < 0 || priority > DispatcherPriority.Send)
172 throw new InvalidEnumArgumentException ("priority");
173 if (priority == DispatcherPriority.Inactive)
174 throw new ArgumentException ("priority can not be inactive", "priority");
175 if (method == null)
176 throw new ArgumentNullException ("method");
178 DispatcherOperation op = new DispatcherOperation (this, priority, method, arg, args);
179 Queue (priority, op);
181 return op;
184 public object Invoke (Delegate method, object[] args)
186 throw new NotImplementedException ();
189 public object Invoke (Delegate method, TimeSpan timeout, object[] args)
191 throw new NotImplementedException ();
194 public object Invoke (Delegate method, TimeSpan timeout, DispatcherPriority priority, object[] args)
196 throw new NotImplementedException ();
199 public object Invoke (Delegate method, DispatcherPriority priority, object[] args)
201 throw new NotImplementedException ();
204 [Browsable (false)]
205 [EditorBrowsable (EditorBrowsableState.Never)]
206 public object Invoke (DispatcherPriority priority, Delegate method)
208 if (priority < 0 || priority > DispatcherPriority.Send)
209 throw new InvalidEnumArgumentException ("priority");
210 if (priority == DispatcherPriority.Inactive)
211 throw new ArgumentException ("priority can not be inactive", "priority");
212 if (method == null)
213 throw new ArgumentNullException ("method");
215 DispatcherOperation op = new DispatcherOperation (this, priority, method);
216 Queue (priority, op);
217 PushFrame (new DispatcherFrame ());
219 throw new NotImplementedException ();
222 [Browsable (false)]
223 [EditorBrowsable (EditorBrowsableState.Never)]
224 public object Invoke (DispatcherPriority priority, Delegate method, object arg)
226 if (priority < 0 || priority > DispatcherPriority.Send)
227 throw new InvalidEnumArgumentException ("priority");
228 if (priority == DispatcherPriority.Inactive)
229 throw new ArgumentException ("priority can not be inactive", "priority");
230 if (method == null)
231 throw new ArgumentNullException ("method");
233 Queue (priority, new DispatcherOperation (this, priority, method, arg));
234 throw new NotImplementedException ();
237 [Browsable (false)]
238 [EditorBrowsable (EditorBrowsableState.Never)]
239 public object Invoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
241 if (priority < 0 || priority > DispatcherPriority.Send)
242 throw new InvalidEnumArgumentException ("priority");
243 if (priority == DispatcherPriority.Inactive)
244 throw new ArgumentException ("priority can not be inactive", "priority");
245 if (method == null)
246 throw new ArgumentNullException ("method");
248 Queue (priority, new DispatcherOperation (this, priority, method, arg, args));
250 throw new NotImplementedException ();
253 [Browsable (false)]
254 [EditorBrowsable (EditorBrowsableState.Never)]
255 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method)
257 throw new NotImplementedException ();
260 [Browsable (false)]
261 [EditorBrowsable (EditorBrowsableState.Never)]
262 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)
264 throw new NotImplementedException ();
267 [Browsable (false)]
268 [EditorBrowsable (EditorBrowsableState.Never)]
269 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)
271 throw new NotImplementedException ();
274 void Queue (DispatcherPriority priority, DispatcherOperation x)
276 int p = ((int) priority);
277 PokableQueue q = priority_queues [p];
279 lock (q){
280 int flag = 1 << p;
281 q.Enqueue (x);
282 queue_bits |= flag;
284 hooks.EmitOperationPosted (x);
286 if (Thread.CurrentThread != base_thread)
287 wait.Set ();
290 internal void Reprioritize (DispatcherOperation op, DispatcherPriority oldpriority)
292 int oldp = (int) oldpriority;
293 PokableQueue q = priority_queues [oldp];
295 lock (q){
296 q.Remove (op);
298 Queue (op.Priority, op);
299 hooks.EmitOperationPriorityChanged (op);
302 public static Dispatcher CurrentDispatcher {
303 get {
304 lock (olock){
305 Thread t = Thread.CurrentThread;
306 Dispatcher dis = FromThread (t);
308 if (dis != null)
309 return dis;
311 dis = new Dispatcher (t);
312 dispatchers [t] = dis;
313 return dis;
318 public static Dispatcher FromThread (Thread thread)
320 Dispatcher dis;
322 if (dispatchers.TryGetValue (thread, out dis))
323 return dis;
325 return null;
328 public Thread Thread {
329 get {
330 return base_thread;
334 [SecurityCritical]
335 public static void Run ()
337 PushFrame (main_execution_frame);
340 [SecurityCritical]
341 public static void PushFrame (DispatcherFrame frame)
343 if (frame == null)
344 throw new ArgumentNullException ("frame");
346 Dispatcher dis = CurrentDispatcher;
348 if (dis.HasShutdownFinished)
349 throw new InvalidOperationException ("The Dispatcher has shut down");
350 if (frame.dispatcher != null)
351 throw new InvalidOperationException ("Frame is already running on a different dispatcher");
352 if ((dis.flags & Flags.Disabled) != 0)
353 throw new InvalidOperationException ("Dispatcher processing has been disabled");
355 frame.ParentFrame = dis.current_frame;
356 dis.current_frame = frame;
358 frame.dispatcher = dis;
360 dis.RunFrame (frame);
363 void PerformShutdown ()
365 EventHandler h;
367 h = ShutdownStarted;
368 if (h != null)
369 h (this, new EventArgs ());
371 flags |= Flags.Shutdown;
373 h = ShutdownFinished;
374 if (h != null)
375 h (this, new EventArgs ());
377 priority_queues = null;
378 wait = null;
381 void RunFrame (DispatcherFrame frame)
383 do {
384 while (queue_bits != 0){
385 for (int i = TOP_PRIO; i > 0 && queue_bits != 0; i--){
386 int current_bit = queue_bits & (1 << i);
387 if (current_bit != 0){
388 PokableQueue q = priority_queues [i];
390 do {
391 DispatcherOperation task;
393 lock (q){
394 task = (DispatcherOperation) q.Dequeue ();
397 task.Invoke ();
400 // call hooks.
402 if (task.Status == DispatcherOperationStatus.Aborted)
403 hooks.EmitOperationAborted (task);
404 else
405 hooks.EmitOperationCompleted (task);
407 if (!frame.Continue)
408 return;
410 if (HasShutdownStarted){
411 PerformShutdown ();
412 return;
415 // if we are done with this queue, leave.
416 lock (q){
417 if (q.Count == 0){
418 queue_bits &= ~(1 << i);
419 break;
424 // If a higher-priority task comes in, go do that
426 if (current_bit < (queue_bits & ~current_bit))
427 break;
428 } while (true);
432 hooks.EmitInactive ();
434 wait.WaitOne ();
435 wait.Reset ();
436 } while (frame.Continue);
439 [EditorBrowsable (EditorBrowsableState.Advanced)]
440 public DispatcherHooks Hooks {
441 [SecurityCritical]
442 get { throw new NotImplementedException (); }
445 public bool HasShutdownStarted {
446 get {
447 return (flags & Flags.ShutdownStarted) != 0;
451 public bool HasShutdownFinished {
452 get {
453 return (flags & Flags.Shutdown) != 0;
458 // Do no work here, so that any events are thrown on the owning thread
460 [SecurityCritical]
461 public void InvokeShutdown ()
463 flags |= Flags.ShutdownStarted;
466 [SecurityCritical]
467 public void BeginInvokeShutdown (DispatcherPriority priority)
469 throw new NotImplementedException ();
472 [SecurityCritical]
473 public static void ExitAllFrames ()
475 Dispatcher dis = CurrentDispatcher;
477 for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
478 if (frame.exit_on_request)
479 frame.Continue = false;
480 else {
482 // Stop unwinding the frames at the first frame that is
483 // long running
484 break;
489 public DispatcherProcessingDisabled DisableProcessing ()
491 throw new NotImplementedException ();
494 public event EventHandler ShutdownStarted;
495 public event EventHandler ShutdownFinished;
496 public event DispatcherUnhandledExceptionEventHandler UnhandledException;
497 public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
500 internal class PokableQueue {
501 const int initial_capacity = 32;
503 int size, head, tail;
504 object [] array;
506 internal PokableQueue (int capacity)
508 array = new object [capacity];
511 internal PokableQueue () : this (initial_capacity)
515 public void Enqueue (object obj)
517 if (size == array.Length)
518 Grow ();
519 array[tail] = obj;
520 tail = (tail+1) % array.Length;
521 size++;
524 public object Dequeue ()
526 if (size < 1)
527 throw new InvalidOperationException ();
528 object result = array[head];
529 array [head] = null;
530 head = (head + 1) % array.Length;
531 size--;
532 return result;
535 void Grow () {
536 int newc = array.Length * 2;
537 object[] new_contents = new object[newc];
538 array.CopyTo (new_contents, 0);
539 array = new_contents;
540 head = 0;
541 tail = head + size;
544 public int Count {
545 get {
546 return size;
550 public void Remove (object obj)
552 for (int i = 0; i < size; i++){
553 if (array [(head+i) % array.Length] == obj){
554 for (int j = i; j < size-i; j++)
555 array [(head +j) % array.Length] = array [(head+j+1) % array.Length];
556 size--;
557 if (size < 0)
558 size = array.Length-1;
559 tail--;