Add [Category ("NotWorking")] to failing test.
[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                         PushFrame (main_execution_frame);
338                 }
339                 
340                 [SecurityCritical]
341                 public static void PushFrame (DispatcherFrame frame)
342                 {
343                         if (frame == null)
344                                 throw new ArgumentNullException ("frame");
345
346                         Dispatcher dis = CurrentDispatcher;
347
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");
354
355                         frame.ParentFrame = dis.current_frame;
356                         dis.current_frame = frame;
357                         
358                         frame.dispatcher = dis;
359
360                         dis.RunFrame (frame);
361                 }
362
363                 void PerformShutdown ()
364                 {
365                         EventHandler h;
366                         
367                         h = ShutdownStarted;
368                         if (h != null)
369                                 h (this, new EventArgs ());
370                         
371                         flags |= Flags.Shutdown;
372                         
373                         h = ShutdownFinished;
374                         if (h != null)
375                                 h (this, new EventArgs ());
376
377                         priority_queues = null;
378                         wait = null;
379                 }
380                 
381                 void RunFrame (DispatcherFrame frame)
382                 {
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];
389
390                                                         do {
391                                                                 DispatcherOperation task;
392                                                                 
393                                                                 lock (q){
394                                                                         task = (DispatcherOperation) q.Dequeue ();
395                                                                 }
396                                                                 
397                                                                 task.Invoke ();
398
399                                                                 //
400                                                                 // call hooks.
401                                                                 //
402                                                                 if (task.Status == DispatcherOperationStatus.Aborted)
403                                                                         hooks.EmitOperationAborted (task);
404                                                                 else
405                                                                         hooks.EmitOperationCompleted (task);
406
407                                                                 if (!frame.Continue)
408                                                                         return;
409                                                                 
410                                                                 if (HasShutdownStarted){
411                                                                         PerformShutdown ();
412                                                                         return;
413                                                                 }
414                                                                 
415                                                                 // if we are done with this queue, leave.
416                                                                 lock (q){
417                                                                         if (q.Count == 0){
418                                                                                 queue_bits &= ~(1 << i);
419                                                                                 break;
420                                                                         }
421                                                                 }
422
423                                                                 //
424                                                                 // If a higher-priority task comes in, go do that
425                                                                 //
426                                                                 if (current_bit < (queue_bits & ~current_bit))
427                                                                         break;
428                                                         } while (true);
429                                                 }
430                                         }
431                                 }
432                                 hooks.EmitInactive ();
433                                 
434                                 wait.WaitOne ();
435                                 wait.Reset ();
436                         } while (frame.Continue);
437                 }
438
439                 [EditorBrowsable (EditorBrowsableState.Advanced)]
440                 public DispatcherHooks Hooks {
441                         [SecurityCritical]
442                         get { throw new NotImplementedException (); }
443                 }
444
445                 public bool HasShutdownStarted {
446                         get {
447                                 return (flags & Flags.ShutdownStarted) != 0;
448                         }
449                 }
450
451                 public bool HasShutdownFinished {
452                         get {
453                                 return (flags & Flags.Shutdown) != 0;
454                         }
455                 }
456
457                 //
458                 // Do no work here, so that any events are thrown on the owning thread
459                 //
460                 [SecurityCritical]
461                 public void InvokeShutdown ()
462                 {
463                         flags |= Flags.ShutdownStarted;
464                 }
465
466                 [SecurityCritical]
467                 public void BeginInvokeShutdown (DispatcherPriority priority)
468                 {
469                         throw new NotImplementedException ();
470                 }
471
472                 [SecurityCritical]
473                 public static void ExitAllFrames ()
474                 {
475                         Dispatcher dis = CurrentDispatcher;
476                         
477                         for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
478                                 if (frame.exit_on_request)
479                                         frame.Continue = false;
480                                 else {
481                                         //
482                                         // Stop unwinding the frames at the first frame that is
483                                         // long running
484                                         break;
485                                 }
486                         }
487                 }
488
489                 public DispatcherProcessingDisabled DisableProcessing ()
490                 {
491                         throw new NotImplementedException ();
492                 }
493
494                 public event EventHandler ShutdownStarted;
495                 public event EventHandler ShutdownFinished;
496                 public event DispatcherUnhandledExceptionEventHandler UnhandledException;
497                 public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
498         }
499
500         internal class PokableQueue {
501                 const int initial_capacity = 32;
502
503                 int size, head, tail;
504                 object [] array;
505
506                 internal PokableQueue (int capacity) 
507                 {
508                         array = new object [capacity];
509                 }
510
511                 internal PokableQueue () : this (initial_capacity)
512                 {
513                 }
514
515                 public void Enqueue (object obj)
516                 {
517                         if (size == array.Length)
518                                 Grow ();
519                         array[tail] = obj;
520                         tail = (tail+1) % array.Length;
521                         size++;
522                 }
523
524                 public object Dequeue ()
525                 {
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;
533                 }
534
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;
542                 }
543
544                 public int Count {
545                         get {
546                                 return size;
547                         }
548                 }
549
550                 public void Remove (object obj)
551                 {
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--;
560                                 }
561                         }
562                 }
563         }
564 }