Merge pull request #900 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / WindowsBase / System.Windows.Threading / Dispatcher.cs
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 //
47 // Authors:
48 //      Miguel de Icaza (miguel@novell.com)
49 //
50 using System;
51 using System.Collections;
52 using System.Collections.Generic;
53 using System.ComponentModel;
54 using System.Security;
55 using System.Threading;
56
57 namespace System.Windows.Threading {
58
59         [Flags]
60         internal enum Flags {
61                 ShutdownStarted = 1,
62                 Shutdown = 2,
63                 Disabled = 4
64         }
65         
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 ();
70                 
71                 const int TOP_PRIO = (int)DispatcherPriority.Send;
72                 Thread base_thread;
73                 PokableQueue [] priority_queues = new PokableQueue [TOP_PRIO+1];
74
75                 Flags flags;
76                 int queue_bits;
77
78                 //
79                 // Used to notify the dispatcher thread that new data is available
80                 //
81                 EventWaitHandle wait;
82
83                 //
84                 // The hooks for this Dispatcher
85                 //
86                 DispatcherHooks hooks;
87
88                 //
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;
93                 
94                 Dispatcher (Thread t)
95                 {
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);
101                 }
102
103                 [EditorBrowsable (EditorBrowsableState.Never)]
104                 public bool CheckAccess ()
105                 {
106                         return Thread.CurrentThread == base_thread;
107                 }
108
109                 [EditorBrowsable (EditorBrowsableState.Never)]
110                 public void VerifyAccess ()
111                 {
112                         if (Thread.CurrentThread != base_thread)
113                                 throw new InvalidOperationException ("Invoked from a different thread");
114                 }
115
116                 public static void ValidatePriority (DispatcherPriority priority, string parameterName)
117                 {
118                         if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
119                                 throw new InvalidEnumArgumentException (parameterName);
120                 }
121
122                 public DispatcherOperation BeginInvoke (Delegate method, params object[] args)
123                 {
124                         throw new NotImplementedException ();
125                 }
126                 
127                 public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, params object[] args)
128                 {
129                         throw new NotImplementedException ();
130                 }
131
132                 [Browsable (false)]
133                 [EditorBrowsable (EditorBrowsableState.Never)]
134                 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method)
135                 {
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");
142
143                         DispatcherOperation op = new DispatcherOperation (this, priority, method);
144                         Queue (priority, op);
145
146                         return op;
147                 }
148
149                 [Browsable (false)]
150                 [EditorBrowsable (EditorBrowsableState.Never)]
151                 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg)
152                 {
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");
159
160                         DispatcherOperation op = new DispatcherOperation (this, priority, method, arg);
161                         
162                         Queue (priority, op);
163                         
164                         return op;
165                 }
166                 
167                 [Browsable (false)]
168                 [EditorBrowsable (EditorBrowsableState.Never)]
169                 public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
170                 {
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");
177
178                         DispatcherOperation op = new DispatcherOperation (this, priority, method, arg, args);
179                         Queue (priority, op);
180
181                         return op;
182                 }
183
184                 public object Invoke (Delegate method, params object[] args)
185                 {
186                         throw new NotImplementedException ();
187                 }
188
189                 public object Invoke (Delegate method, TimeSpan timeout, params object[] args)
190                 {
191                         throw new NotImplementedException ();
192                 }
193
194                 public object Invoke (Delegate method, TimeSpan timeout, DispatcherPriority priority, params object[] args)
195                 {
196                         throw new NotImplementedException ();
197                 }
198
199                 public object Invoke (Delegate method, DispatcherPriority priority, params object[] args)
200                 {
201                         throw new NotImplementedException ();
202                 }
203
204                 [Browsable (false)]
205                 [EditorBrowsable (EditorBrowsableState.Never)]
206                 public object Invoke (DispatcherPriority priority, Delegate method)
207                 {
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");
214
215                         DispatcherOperation op = new DispatcherOperation (this, priority, method);
216                         Queue (priority, op);
217                         PushFrame (new DispatcherFrame ());
218                         
219                         throw new NotImplementedException ();
220                 }
221
222                 [Browsable (false)]
223                 [EditorBrowsable (EditorBrowsableState.Never)]
224                 public object Invoke (DispatcherPriority priority, Delegate method, object arg)
225                 {
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");
232
233                         Queue (priority, new DispatcherOperation (this, priority, method, arg));
234                         throw new NotImplementedException ();
235                 }
236                 
237                 [Browsable (false)]
238                 [EditorBrowsable (EditorBrowsableState.Never)]
239                 public object Invoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
240                 {
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");
247
248                         Queue (priority, new DispatcherOperation (this, priority, method, arg, args));
249
250                         throw new NotImplementedException ();
251                 }
252
253                 [Browsable (false)]
254                 [EditorBrowsable (EditorBrowsableState.Never)]
255                 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method)
256                 {
257                         throw new NotImplementedException ();
258                 }
259
260                 [Browsable (false)]
261                 [EditorBrowsable (EditorBrowsableState.Never)]
262                 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)
263                 {
264                         throw new NotImplementedException ();
265                 }
266
267                 [Browsable (false)]
268                 [EditorBrowsable (EditorBrowsableState.Never)]
269                 public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)
270                 {
271                         throw new NotImplementedException ();
272                 }
273
274                 void Queue (DispatcherPriority priority, DispatcherOperation x)
275                 {
276                         int p = ((int) priority);
277                         PokableQueue q = priority_queues [p];
278                         
279                         lock (q){
280                                 int flag = 1 << p;
281                                 q.Enqueue (x);
282                                 queue_bits |= flag;
283                         }
284                         hooks.EmitOperationPosted (x);
285
286                         if (Thread.CurrentThread != base_thread)
287                                 wait.Set ();
288                 }
289
290                 internal void Reprioritize (DispatcherOperation op, DispatcherPriority oldpriority)
291                 {
292                         int oldp = (int) oldpriority;
293                         PokableQueue q = priority_queues [oldp];
294
295                         lock (q){
296                                 q.Remove (op);
297                         }
298                         Queue (op.Priority, op);
299                         hooks.EmitOperationPriorityChanged (op);
300                 }
301                 
302                 public static Dispatcher CurrentDispatcher {
303                         get {
304                                 lock (olock){
305                                         Thread t = Thread.CurrentThread;
306                                         Dispatcher dis = FromThread (t);
307
308                                         if (dis != null)
309                                                 return dis;
310                                 
311                                         dis = new Dispatcher (t);
312                                         dispatchers [t] = dis;
313                                         return dis;
314                                 }
315                         }
316                 }
317
318                 public static Dispatcher FromThread (Thread thread)
319                 {
320                         Dispatcher dis;
321                         
322                         if (dispatchers.TryGetValue (thread, out dis))
323                                 return dis;
324
325                         return null;
326                 }
327
328                 public Thread Thread {
329                         get {
330                                 return base_thread;
331                         }
332                 }
333
334                 [SecurityCritical]
335                 public static void Run ()
336                 {
337                         // Set Continue, because the previous run could clean
338                         // this flag by Dispatcher.ExitAllFrames.
339                         main_execution_frame.Continue = true;
340                         PushFrame (main_execution_frame);
341                 }
342                 
343                 [SecurityCritical]
344                 public static void PushFrame (DispatcherFrame frame)
345                 {
346                         if (frame == null)
347                                 throw new ArgumentNullException ("frame");
348
349                         Dispatcher dis = CurrentDispatcher;
350
351                         if (dis.HasShutdownFinished)
352                                 throw new InvalidOperationException ("The Dispatcher has shut down");
353                         if (frame.dispatcher != null)
354                                 throw new InvalidOperationException ("Frame is already running on a different dispatcher");
355                         if ((dis.flags & Flags.Disabled) != 0)
356                                 throw new InvalidOperationException ("Dispatcher processing has been disabled");
357
358                         frame.ParentFrame = dis.current_frame;
359                         dis.current_frame = frame;
360                         
361                         frame.dispatcher = dis;
362
363                         dis.RunFrame (frame);
364
365                         frame.dispatcher = null;
366                         dis.current_frame = frame.ParentFrame;
367                         frame.ParentFrame = null;
368                 }
369
370                 void PerformShutdown ()
371                 {
372                         EventHandler h;
373                         
374                         h = ShutdownStarted;
375                         if (h != null)
376                                 h (this, new EventArgs ());
377                         
378                         flags |= Flags.Shutdown;
379                         
380                         h = ShutdownFinished;
381                         if (h != null)
382                                 h (this, new EventArgs ());
383
384                         priority_queues = null;
385                         wait = null;
386                 }
387                 
388                 void RunFrame (DispatcherFrame frame)
389                 {
390                         while (frame.Continue) {
391                                 while (queue_bits != 0){
392                                         for (int i = TOP_PRIO; i > 0 && queue_bits != 0; i--){
393                                                 int current_bit = queue_bits & (1 << i);
394                                                 if (current_bit != 0){
395                                                         PokableQueue q = priority_queues [i];
396
397                                                         do {
398                                                                 DispatcherOperation task;
399                                                                 
400                                                                 lock (q){
401                                                                         // if we are done with this queue, leave.
402                                                                         if (q.Count == 0){
403                                                                                 queue_bits &= ~current_bit;
404                                                                                 break;
405                                                                         }
406                                                                         task = (DispatcherOperation) q.Dequeue ();
407                                                                 }
408                                                                 
409                                                                 task.Invoke ();
410
411                                                                 //
412                                                                 // call hooks.
413                                                                 //
414                                                                 if (task.Status == DispatcherOperationStatus.Aborted)
415                                                                         hooks.EmitOperationAborted (task);
416                                                                 else
417                                                                         hooks.EmitOperationCompleted (task);
418
419                                                                 if (!frame.Continue)
420                                                                         return;
421                                                                 
422                                                                 if (HasShutdownStarted){
423                                                                         PerformShutdown ();
424                                                                         return;
425                                                                 }
426
427                                                                 //
428                                                                 // If a higher-priority task comes in, go do that
429                                                                 //
430                                                                 if (current_bit < (queue_bits & ~current_bit))
431                                                                 {
432                                                                         i = TOP_PRIO + 1; // for-loop decreases by one
433                                                                         break;
434                                                                 }
435                                                         } while (true);
436                                                 }
437                                         }
438                                 }
439                                 hooks.EmitInactive ();
440                                 
441                                 wait.WaitOne ();
442                                 wait.Reset ();
443                         }
444                 }
445
446                 [EditorBrowsable (EditorBrowsableState.Advanced)]
447                 public DispatcherHooks Hooks {
448                         [SecurityCritical]
449                         get { throw new NotImplementedException (); }
450                 }
451
452                 public bool HasShutdownStarted {
453                         get {
454                                 return (flags & Flags.ShutdownStarted) != 0;
455                         }
456                 }
457
458                 public bool HasShutdownFinished {
459                         get {
460                                 return (flags & Flags.Shutdown) != 0;
461                         }
462                 }
463
464                 //
465                 // Do no work here, so that any events are thrown on the owning thread
466                 //
467                 [SecurityCritical]
468                 public void InvokeShutdown ()
469                 {
470                         flags |= Flags.ShutdownStarted;
471                 }
472
473                 [SecurityCritical]
474                 public void BeginInvokeShutdown (DispatcherPriority priority)
475                 {
476                         throw new NotImplementedException ();
477                 }
478
479                 [SecurityCritical]
480                 public static void ExitAllFrames ()
481                 {
482                         Dispatcher dis = CurrentDispatcher;
483                         
484                         for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
485                                 if (frame.exit_on_request)
486                                         frame.Continue = false;
487                                 else {
488                                         //
489                                         // Stop unwinding the frames at the first frame that is
490                                         // long running
491                                         break;
492                                 }
493                         }
494                 }
495
496                 public DispatcherProcessingDisabled DisableProcessing ()
497                 {
498                         throw new NotImplementedException ();
499                 }
500
501                 public event EventHandler ShutdownStarted;
502                 public event EventHandler ShutdownFinished;
503                 public event DispatcherUnhandledExceptionEventHandler UnhandledException;
504                 public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
505         }
506
507         internal class PokableQueue {
508                 const int initial_capacity = 32;
509
510                 int size, head, tail;
511                 object [] array;
512
513                 internal PokableQueue (int capacity) 
514                 {
515                         array = new object [capacity];
516                 }
517
518                 internal PokableQueue () : this (initial_capacity)
519                 {
520                 }
521
522                 public void Enqueue (object obj)
523                 {
524                         if (size == array.Length)
525                                 Grow ();
526                         array[tail] = obj;
527                         tail = (tail+1) % array.Length;
528                         size++;
529                 }
530
531                 public object Dequeue ()
532                 {
533                         if (size < 1)
534                                 throw new InvalidOperationException ();
535                         object result = array[head];
536                         array [head] = null;
537                         head = (head + 1) % array.Length;
538                         size--;
539                         return result;
540                 }
541
542                 void Grow () {
543                         int newc = array.Length * 2;
544                         object[] new_contents = new object[newc];
545                         array.CopyTo (new_contents, 0);
546                         array = new_contents;
547                         head = 0;
548                         tail = head + size;
549                 }
550
551                 public int Count {
552                         get {
553                                 return size;
554                         }
555                 }
556
557                 public void Remove (object obj)
558                 {
559                         for (int i = 0; i < size; i++){
560                                 if (array [(head+i) % array.Length] == obj){
561                                         for (int j = i; j < size-i; j++)
562                                                 array [(head +j) % array.Length] = array [(head+j+1) % array.Length];
563                                         size--;
564                                         if (size < 0)
565                                                 size = array.Length-1;
566                                         tail--;
567                                 }
568                         }
569                 }
570         }
571 }