e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
590 lines
16 KiB
C#
590 lines
16 KiB
C#
// TODO:
|
|
// DispatcherObject returned by BeginInvoke must allow:
|
|
// * Waiting until delegate is invoked.
|
|
// See: BeginInvoke documentation for details
|
|
//
|
|
// Implement the "Invoke" methods, they are currently not working.
|
|
//
|
|
// Add support for disabling the dispatcher and resuming it.
|
|
// Add support for Waiting for new tasks to be pushed, so that we dont busy loop.
|
|
// Add support for aborting an operation (emit the hook.operationaborted too)
|
|
//
|
|
// Very confusing information about Shutdown: it states that shutdown is
|
|
// not over, until all events are unwinded, and also states that all events
|
|
// are aborted at that point. See 'Dispatcher.InvokeShutdown' docs,
|
|
//
|
|
// Testing reveals that
|
|
// -> InvokeShutdown() stops processing, even if events are available,
|
|
// there is no "unwinding" of events, even of higher priority events,
|
|
// they are just ignored.
|
|
//
|
|
// The documentation for the Dispatcher family is poorly written, complete
|
|
// sections are cut-and-pasted that add no value and the important pieces
|
|
// like (what is a frame) is not on the APIs, but scattered everywhere else
|
|
//
|
|
// -----------------------------------------------------------------------
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
|
|
// Copyright (c) 2016 Quamotion (http://quamotion.mobi)
|
|
//
|
|
// Authors:
|
|
// Miguel de Icaza (miguel@novell.com)
|
|
// Frederik Carlier (frederik.carlier@quamotion.mobi)
|
|
//
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Security;
|
|
using System.Threading;
|
|
|
|
namespace System.Windows.Threading {
|
|
|
|
[Flags]
|
|
internal enum Flags {
|
|
ShutdownStarted = 1,
|
|
Shutdown = 2,
|
|
Disabled = 4
|
|
}
|
|
|
|
public sealed class Dispatcher {
|
|
static Dictionary<Thread, Dispatcher> dispatchers = new Dictionary<Thread, Dispatcher> ();
|
|
static object olock = new object ();
|
|
static DispatcherFrame main_execution_frame = new DispatcherFrame ();
|
|
|
|
const int TOP_PRIO = (int)DispatcherPriority.Send;
|
|
Thread base_thread;
|
|
PokableQueue [] priority_queues = new PokableQueue [TOP_PRIO+1];
|
|
|
|
Flags flags;
|
|
int queue_bits;
|
|
|
|
//
|
|
// Used to notify the dispatcher thread that new data is available
|
|
//
|
|
EventWaitHandle wait;
|
|
|
|
//
|
|
// The hooks for this Dispatcher
|
|
//
|
|
DispatcherHooks hooks;
|
|
|
|
//
|
|
// The current DispatcherFrame active in a given Dispatcher, we use this to
|
|
// keep a linked list of all active frames, so we can "ExitAll" frames when
|
|
// requested
|
|
DispatcherFrame current_frame;
|
|
|
|
Dispatcher (Thread t)
|
|
{
|
|
base_thread = t;
|
|
for (int i = 1; i <= (int) DispatcherPriority.Send; i++)
|
|
priority_queues [i] = new PokableQueue ();
|
|
wait = new EventWaitHandle (false, EventResetMode.AutoReset);
|
|
hooks = new DispatcherHooks (this);
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public bool CheckAccess ()
|
|
{
|
|
return Thread.CurrentThread == base_thread;
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public void VerifyAccess ()
|
|
{
|
|
if (Thread.CurrentThread != base_thread)
|
|
throw new InvalidOperationException ("Invoked from a different thread");
|
|
}
|
|
|
|
public static void ValidatePriority (DispatcherPriority priority, string parameterName)
|
|
{
|
|
if (priority < DispatcherPriority.Inactive || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException (parameterName);
|
|
}
|
|
|
|
public DispatcherOperation BeginInvoke (Delegate method, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
DispatcherOperation op = new DispatcherOperation (this, priority, method);
|
|
Queue (priority, op);
|
|
|
|
return op;
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
DispatcherOperation op = new DispatcherOperation (this, priority, method, arg);
|
|
|
|
Queue (priority, op);
|
|
|
|
return op;
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public DispatcherOperation BeginInvoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
DispatcherOperation op = new DispatcherOperation (this, priority, method, arg, args);
|
|
Queue (priority, op);
|
|
|
|
return op;
|
|
}
|
|
|
|
public DispatcherOperation InvokeAsync (Action callback)
|
|
{
|
|
return this.BeginInvoke(callback);
|
|
}
|
|
|
|
public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority)
|
|
{
|
|
return this.BeginInvoke(callback, priority);
|
|
}
|
|
|
|
public DispatcherOperation InvokeAsync (Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
|
|
{
|
|
return this.BeginInvoke(callback, priority);
|
|
}
|
|
|
|
public object Invoke (Delegate method, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public object Invoke (Delegate method, TimeSpan timeout, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public object Invoke (Delegate method, TimeSpan timeout, DispatcherPriority priority, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public object Invoke (Delegate method, DispatcherPriority priority, params object[] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, Delegate method)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
DispatcherOperation op = new DispatcherOperation (this, priority, method);
|
|
Queue (priority, op);
|
|
PushFrame (new DispatcherFrame ());
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, Delegate method, object arg)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
Queue (priority, new DispatcherOperation (this, priority, method, arg));
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, Delegate method, object arg, params object [] args)
|
|
{
|
|
if (priority < 0 || priority > DispatcherPriority.Send)
|
|
throw new InvalidEnumArgumentException ("priority");
|
|
if (priority == DispatcherPriority.Inactive)
|
|
throw new ArgumentException ("priority can not be inactive", "priority");
|
|
if (method == null)
|
|
throw new ArgumentNullException ("method");
|
|
|
|
Queue (priority, new DispatcherOperation (this, priority, method, arg, args));
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public object Invoke (DispatcherPriority priority, TimeSpan timeout, Delegate method, object arg, params object [] args)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
void Queue (DispatcherPriority priority, DispatcherOperation x)
|
|
{
|
|
int p = ((int) priority);
|
|
PokableQueue q = priority_queues [p];
|
|
|
|
lock (q){
|
|
int flag = 1 << p;
|
|
q.Enqueue (x);
|
|
queue_bits |= flag;
|
|
}
|
|
hooks.EmitOperationPosted (x);
|
|
|
|
if (Thread.CurrentThread != base_thread)
|
|
wait.Set ();
|
|
}
|
|
|
|
internal void Reprioritize (DispatcherOperation op, DispatcherPriority oldpriority)
|
|
{
|
|
int oldp = (int) oldpriority;
|
|
PokableQueue q = priority_queues [oldp];
|
|
|
|
lock (q){
|
|
q.Remove (op);
|
|
}
|
|
Queue (op.Priority, op);
|
|
hooks.EmitOperationPriorityChanged (op);
|
|
}
|
|
|
|
public static Dispatcher CurrentDispatcher {
|
|
get {
|
|
lock (olock){
|
|
Thread t = Thread.CurrentThread;
|
|
Dispatcher dis = FromThread (t);
|
|
|
|
if (dis != null)
|
|
return dis;
|
|
|
|
dis = new Dispatcher (t);
|
|
dispatchers [t] = dis;
|
|
return dis;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Dispatcher FromThread (Thread thread)
|
|
{
|
|
Dispatcher dis;
|
|
|
|
if (dispatchers.TryGetValue (thread, out dis))
|
|
return dis;
|
|
|
|
return null;
|
|
}
|
|
|
|
public Thread Thread {
|
|
get {
|
|
return base_thread;
|
|
}
|
|
}
|
|
|
|
[SecurityCritical]
|
|
public static void Run ()
|
|
{
|
|
// Set Continue, because the previous run could clean
|
|
// this flag by Dispatcher.ExitAllFrames.
|
|
main_execution_frame.Continue = true;
|
|
PushFrame (main_execution_frame);
|
|
}
|
|
|
|
[SecurityCritical]
|
|
public static void PushFrame (DispatcherFrame frame)
|
|
{
|
|
if (frame == null)
|
|
throw new ArgumentNullException ("frame");
|
|
|
|
Dispatcher dis = CurrentDispatcher;
|
|
|
|
if (dis.HasShutdownFinished)
|
|
throw new InvalidOperationException ("The Dispatcher has shut down");
|
|
if (frame.dispatcher != null)
|
|
throw new InvalidOperationException ("Frame is already running on a different dispatcher");
|
|
if ((dis.flags & Flags.Disabled) != 0)
|
|
throw new InvalidOperationException ("Dispatcher processing has been disabled");
|
|
|
|
frame.ParentFrame = dis.current_frame;
|
|
dis.current_frame = frame;
|
|
|
|
frame.dispatcher = dis;
|
|
|
|
dis.RunFrame (frame);
|
|
|
|
frame.dispatcher = null;
|
|
dis.current_frame = frame.ParentFrame;
|
|
frame.ParentFrame = null;
|
|
}
|
|
|
|
void PerformShutdown ()
|
|
{
|
|
EventHandler h;
|
|
|
|
h = ShutdownStarted;
|
|
if (h != null)
|
|
h (this, new EventArgs ());
|
|
|
|
flags |= Flags.Shutdown;
|
|
|
|
h = ShutdownFinished;
|
|
if (h != null)
|
|
h (this, new EventArgs ());
|
|
|
|
priority_queues = null;
|
|
wait = null;
|
|
}
|
|
|
|
void RunFrame (DispatcherFrame frame)
|
|
{
|
|
while (frame.Continue) {
|
|
while (queue_bits != 0){
|
|
for (int i = TOP_PRIO; i > 0 && queue_bits != 0; i--){
|
|
int current_bit = queue_bits & (1 << i);
|
|
if (current_bit != 0){
|
|
PokableQueue q = priority_queues [i];
|
|
|
|
do {
|
|
DispatcherOperation task;
|
|
|
|
lock (q){
|
|
// if we are done with this queue, leave.
|
|
if (q.Count == 0){
|
|
queue_bits &= ~current_bit;
|
|
break;
|
|
}
|
|
task = (DispatcherOperation) q.Dequeue ();
|
|
}
|
|
|
|
task.Invoke ();
|
|
|
|
//
|
|
// call hooks.
|
|
//
|
|
if (task.Status == DispatcherOperationStatus.Aborted)
|
|
hooks.EmitOperationAborted (task);
|
|
else
|
|
hooks.EmitOperationCompleted (task);
|
|
|
|
if (!frame.Continue)
|
|
return;
|
|
|
|
if (HasShutdownStarted){
|
|
PerformShutdown ();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If a higher-priority task comes in, go do that
|
|
//
|
|
if (current_bit < (queue_bits & ~current_bit))
|
|
{
|
|
i = TOP_PRIO + 1; // for-loop decreases by one
|
|
break;
|
|
}
|
|
} while (true);
|
|
}
|
|
}
|
|
}
|
|
hooks.EmitInactive ();
|
|
|
|
wait.WaitOne ();
|
|
wait.Reset ();
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
|
public DispatcherHooks Hooks {
|
|
[SecurityCritical]
|
|
get { throw new NotImplementedException (); }
|
|
}
|
|
|
|
public bool HasShutdownStarted {
|
|
get {
|
|
return (flags & Flags.ShutdownStarted) != 0;
|
|
}
|
|
}
|
|
|
|
public bool HasShutdownFinished {
|
|
get {
|
|
return (flags & Flags.Shutdown) != 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do no work here, so that any events are thrown on the owning thread
|
|
//
|
|
[SecurityCritical]
|
|
public void InvokeShutdown ()
|
|
{
|
|
flags |= Flags.ShutdownStarted;
|
|
}
|
|
|
|
[SecurityCritical]
|
|
public void BeginInvokeShutdown (DispatcherPriority priority)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[SecurityCritical]
|
|
public static void ExitAllFrames ()
|
|
{
|
|
Dispatcher dis = CurrentDispatcher;
|
|
|
|
for (DispatcherFrame frame = dis.current_frame; frame != null; frame = frame.ParentFrame){
|
|
if (frame.exit_on_request)
|
|
frame.Continue = false;
|
|
else {
|
|
//
|
|
// Stop unwinding the frames at the first frame that is
|
|
// long running
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public DispatcherProcessingDisabled DisableProcessing ()
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public event EventHandler ShutdownStarted;
|
|
public event EventHandler ShutdownFinished;
|
|
public event DispatcherUnhandledExceptionEventHandler UnhandledException;
|
|
public event DispatcherUnhandledExceptionFilterEventHandler UnhandledExceptionFilter;
|
|
}
|
|
|
|
internal class PokableQueue {
|
|
const int initial_capacity = 32;
|
|
|
|
int size, head, tail;
|
|
object [] array;
|
|
|
|
internal PokableQueue (int capacity)
|
|
{
|
|
array = new object [capacity];
|
|
}
|
|
|
|
internal PokableQueue () : this (initial_capacity)
|
|
{
|
|
}
|
|
|
|
public void Enqueue (object obj)
|
|
{
|
|
if (size == array.Length)
|
|
Grow ();
|
|
array[tail] = obj;
|
|
tail = (tail+1) % array.Length;
|
|
size++;
|
|
}
|
|
|
|
public object Dequeue ()
|
|
{
|
|
if (size < 1)
|
|
throw new InvalidOperationException ();
|
|
object result = array[head];
|
|
array [head] = null;
|
|
head = (head + 1) % array.Length;
|
|
size--;
|
|
return result;
|
|
}
|
|
|
|
void Grow ()
|
|
{
|
|
int newc = array.Length * 2;
|
|
object[] new_contents = new object[newc];
|
|
array.CopyTo (new_contents, 0);
|
|
array = new_contents;
|
|
head = 0;
|
|
tail = head + size;
|
|
}
|
|
|
|
public int Count {
|
|
get {
|
|
return size;
|
|
}
|
|
}
|
|
|
|
public void Remove (object obj)
|
|
{
|
|
for (int i = 0; i < size; i++){
|
|
if (array [(head+i) % array.Length] == obj){
|
|
for (int j = i; j < size-i; j++)
|
|
array [(head +j) % array.Length] = array [(head+j+1) % array.Length];
|
|
size--;
|
|
if (size < 0)
|
|
size = array.Length-1;
|
|
tail--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|