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