diff --git a/Perspex.Application/Application.cs b/Perspex.Application/Application.cs
index 65d94db76a..8247149225 100644
--- a/Perspex.Application/Application.cs
+++ b/Perspex.Application/Application.cs
@@ -7,6 +7,7 @@
namespace Perspex
{
using System;
+ using System.Threading;
using Perspex.Controls;
using Perspex.Input;
using Perspex.Styling;
@@ -86,9 +87,9 @@ namespace Perspex
public void Run(ICloseable closable)
{
- DispatcherFrame frame = new DispatcherFrame();
- closable.Closed += (s, e) => frame.Continue = false;
- Dispatcher.PushFrame(frame);
+ var source = new CancellationTokenSource();
+ closable.Closed += (s, e) => source.Cancel();
+ Dispatcher.UIThread.MainLoop(source.Token);
}
}
}
diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj
index fcdf7b7ee7..0b224ac578 100644
--- a/Perspex.Base/Perspex.Base.csproj
+++ b/Perspex.Base/Perspex.Base.csproj
@@ -49,9 +49,31 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/Perspex.Base/Platform/IPlatformThreadingInterface.cs
index fa71e33956..1b752ef297 100644
--- a/Perspex.Base/Platform/IPlatformThreadingInterface.cs
+++ b/Perspex.Base/Platform/IPlatformThreadingInterface.cs
@@ -7,14 +7,28 @@
namespace Perspex.Platform
{
using System;
+ using System.Threading;
+ using System.Threading.Tasks;
using Perspex.Threading;
public interface IPlatformThreadingInterface
{
- Dispatcher GetThreadDispatcher();
+ ///
+ /// Process a single message from the windowing system, blocking until one is available.
+ ///
+ void ProcessMessage();
- void KillTimer(object timerHandle);
+ ///
+ /// Starts a timer.
+ ///
+ /// The interval.
+ /// The action to call on each tick.
+ /// An used to stop the timer.
+ IDisposable StartTimer(TimeSpan interval, Action internalTick);
- object StartTimer(TimeSpan interval, Action internalTick);
+ ///
+ /// Sends a message that causes to exit.
+ ///
+ void Wake();
}
}
diff --git a/Perspex.Base/Threading/Dispatcher.cs b/Perspex.Base/Threading/Dispatcher.cs
index 8a832c4b6a..b3a7c1d7ea 100644
--- a/Perspex.Base/Threading/Dispatcher.cs
+++ b/Perspex.Base/Threading/Dispatcher.cs
@@ -1,102 +1,39 @@
// -----------------------------------------------------------------------
//
-// Copyright 2013 MIT Licence. See licence.md for more information.
+// Copyright 2014 MIT Licence. See licence.md for more information.
//
// -----------------------------------------------------------------------
namespace Perspex.Threading
{
using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Security;
using System.Threading;
- using Perspex.Platform;
- using Splat;
+ using System.Threading.Tasks;
+ using Perspex.Win32.Threading;
- public enum DispatcherPriority
+ public class Dispatcher
{
- Invalid = -1,
- Inactive = 0,
- SystemIdle = 1,
- ApplicationIdle = 2,
- ContextIdle = 3,
- Background = 4,
- Input = 5,
- Loaded = 6,
- Render = 7,
- DataBind = 8,
- Normal = 9,
- Send = 10,
- }
+ private static Dispatcher instance = new Dispatcher();
- [Flags]
- internal enum Flags
- {
- ShutdownStarted = 1,
- Shutdown = 2,
- Disabled = 4
- }
-
- public abstract class Dispatcher
- {
- private static DispatcherFrame mainExecutionFrame = new DispatcherFrame();
+ private MainLoop mainLoop = new MainLoop();
- public static Dispatcher CurrentDispatcher
+ private Dispatcher()
{
- get { return Locator.Current.GetService().GetThreadDispatcher(); }
}
- public abstract bool HasShutdownFinished
+ public static Dispatcher UIThread
{
- get;
+ get { return instance; }
}
- public abstract DispatcherFrame CurrentFrame
+ public void MainLoop(CancellationToken cancellationToken)
{
- get;
- set;
+ this.mainLoop.Run(cancellationToken);
}
- public static void PushFrame(DispatcherFrame frame)
+ public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
- if (frame == null)
- {
- throw new ArgumentNullException("frame");
- }
-
- Dispatcher dis = CurrentDispatcher;
-
- if (dis.HasShutdownFinished)
- {
- throw new InvalidOperationException("The Dispatcher has shut down");
- }
-
- if (frame.Running != null)
- {
- throw new InvalidOperationException("Frame is already running on a different dispatcher");
- }
-
- frame.ParentFrame = dis.CurrentFrame;
- dis.CurrentFrame = frame;
-
- frame.Running = dis;
-
- dis.RunFrame(frame);
- }
-
-
- public static void Run()
- {
- PushFrame(mainExecutionFrame);
+ return this.mainLoop.InvokeAsync(action, priority);
}
-
- public abstract DispatcherOperation BeginInvoke(Action method);
-
- public abstract DispatcherOperation BeginInvoke(DispatcherPriority priority, Action method);
-
- protected internal abstract void Reprioritize(DispatcherOperation op, DispatcherPriority oldpriority);
-
- protected abstract void RunFrame(DispatcherFrame frame);
}
}
\ No newline at end of file
diff --git a/Perspex.Base/Threading/DispatcherFrame.cs b/Perspex.Base/Threading/DispatcherFrame.cs
deleted file mode 100644
index 4cb1b3d97a..0000000000
--- a/Perspex.Base/Threading/DispatcherFrame.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// -----------------------------------------------------------------------
-//
-// Copyright 2013 MIT Licence. See licence.md for more information.
-//
-// -----------------------------------------------------------------------
-
-namespace Perspex.Threading
-{
- public class DispatcherFrame
- {
- public DispatcherFrame()
- {
- this.Continue = true;
- this.ExitOnRequest = true;
- }
-
- public bool Continue { get; set; }
-
- public bool ExitOnRequest { get; set; }
-
- public Dispatcher Running { get; set; }
-
- public DispatcherFrame ParentFrame { get; set; }
- }
-}
diff --git a/Perspex.Base/Threading/DispatcherOperation.cs b/Perspex.Base/Threading/DispatcherOperation.cs
deleted file mode 100644
index e9c04c9dbe..0000000000
--- a/Perspex.Base/Threading/DispatcherOperation.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-// -----------------------------------------------------------------------
-//
-// Copyright 2013 MIT Licence. See licence.md for more information.
-//
-// -----------------------------------------------------------------------
-
-namespace Perspex.Threading
-{
- using System;
- using System.Security;
-
- public enum DispatcherOperationStatus
- {
- Pending = 0,
- Aborted = 1,
- Completed = 2,
- Executing = 3
- }
-
- public sealed class DispatcherOperation
- {
- private DispatcherOperationStatus status;
- private DispatcherPriority priority;
- private Dispatcher dispatcher;
- private object result;
- private Delegate delegateMethod;
- private object[] delegateArgs;
-
- public DispatcherOperation(Dispatcher dis, DispatcherPriority prio)
- {
- this.dispatcher = dis;
- this.priority = prio;
- if (this.Dispatcher.HasShutdownFinished)
- {
- this.status = DispatcherOperationStatus.Aborted;
- }
- else
- {
- this.status = DispatcherOperationStatus.Pending;
- }
- }
-
- public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d)
- : this(dis, prio)
- {
- this.delegateMethod = d;
- }
-
- public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg)
- : this(dis, prio)
- {
- this.delegateMethod = d;
- this.delegateArgs = new object[1];
- this.delegateArgs[0] = arg;
- }
-
- public DispatcherOperation(Dispatcher dis, DispatcherPriority prio, Delegate d, object arg, object[] args)
- : this(dis, prio)
- {
- this.delegateMethod = d;
- this.delegateArgs = new object[args.Length + 1];
- this.delegateArgs[0] = arg;
- Array.Copy(args, 1, this.delegateArgs, 0, args.Length);
- }
-
- public event EventHandler Completed;
-
- public DispatcherOperationStatus Status
- {
- get
- {
- return this.status;
- }
-
- internal set
- {
- this.status = value;
- }
- }
-
- public Dispatcher Dispatcher
- {
- get
- {
- return this.dispatcher;
- }
- }
-
- public DispatcherPriority Priority
- {
- get
- {
- return this.priority;
- }
-
- set
- {
- if (this.priority != value)
- {
- DispatcherPriority old = this.priority;
- this.priority = value;
- this.dispatcher.Reprioritize(this, old);
- }
- }
- }
-
- public object Result
- {
- get
- {
- return this.result;
- }
- }
-
- public bool Abort()
- {
- this.status = DispatcherOperationStatus.Aborted;
- throw new NotImplementedException();
- }
-
- public DispatcherOperationStatus Wait()
- {
- if (this.status == DispatcherOperationStatus.Executing)
- {
- throw new InvalidOperationException("Already executing");
- }
-
- throw new NotImplementedException();
- }
-
- [SecurityCritical]
- public DispatcherOperationStatus Wait(TimeSpan timeout)
- {
- if (this.status == DispatcherOperationStatus.Executing)
- {
- throw new InvalidOperationException("Already executing");
- }
-
- throw new NotImplementedException();
- }
-
- public void Invoke()
- {
- this.status = DispatcherOperationStatus.Executing;
-
- if (this.delegateMethod != null)
- {
- this.result = this.delegateMethod.DynamicInvoke(this.delegateArgs);
- }
-
- this.status = DispatcherOperationStatus.Completed;
-
- if (this.Completed != null)
- {
- this.Completed(this, EventArgs.Empty);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Perspex.Base/Threading/DispatcherPriority.cs b/Perspex.Base/Threading/DispatcherPriority.cs
new file mode 100644
index 0000000000..00fb1df484
--- /dev/null
+++ b/Perspex.Base/Threading/DispatcherPriority.cs
@@ -0,0 +1,24 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Threading
+{
+ public enum DispatcherPriority
+ {
+ Invalid = -1,
+ Inactive = 0,
+ SystemIdle = 1,
+ ApplicationIdle = 2,
+ ContextIdle = 3,
+ Background = 4,
+ Input = 5,
+ Loaded = 6,
+ Render = 7,
+ DataBind = 8,
+ Normal = 9,
+ Send = 10,
+ }
+}
diff --git a/Perspex.Base/Threading/DispatcherTimer.cs b/Perspex.Base/Threading/DispatcherTimer.cs
index c1289107bc..b7912fa983 100644
--- a/Perspex.Base/Threading/DispatcherTimer.cs
+++ b/Perspex.Base/Threading/DispatcherTimer.cs
@@ -12,7 +12,7 @@ namespace Perspex.Threading
public class DispatcherTimer
{
- private object timerHandle;
+ private IDisposable timer;
private DispatcherPriority priority;
@@ -21,13 +21,13 @@ namespace Perspex.Threading
public DispatcherTimer()
{
this.priority = DispatcherPriority.Normal;
- this.Dispatcher = Dispatcher.CurrentDispatcher;
+ this.Dispatcher = Dispatcher.UIThread;
}
public DispatcherTimer(DispatcherPriority priority)
{
this.priority = priority;
- this.Dispatcher = Dispatcher.CurrentDispatcher;
+ this.Dispatcher = Dispatcher.UIThread;
}
public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
@@ -46,7 +46,7 @@ namespace Perspex.Threading
~DispatcherTimer()
{
- if (this.timerHandle != null)
+ if (this.timer != null)
{
this.Stop();
}
@@ -80,7 +80,7 @@ namespace Perspex.Threading
{
get
{
- return this.timerHandle != null;
+ return this.timer != null;
}
set
@@ -110,7 +110,7 @@ namespace Perspex.Threading
if (!this.IsEnabled)
{
IPlatformThreadingInterface threading = Locator.Current.GetService();
- this.timerHandle = threading.StartTimer(this.Interval, this.InternalTick);
+ this.timer = threading.StartTimer(this.Interval, this.InternalTick);
}
}
@@ -119,14 +119,14 @@ namespace Perspex.Threading
if (this.IsEnabled)
{
IPlatformThreadingInterface threading = Locator.Current.GetService();
- threading.KillTimer(this.timerHandle);
- this.timerHandle = null;
+ this.timer.Dispose();
+ this.timer = null;
}
}
private void InternalTick()
{
- this.Dispatcher.BeginInvoke(this.priority, (Action)this.RaiseTick);
+ this.Dispatcher.InvokeAsync(this.RaiseTick, this.priority);
}
private void RaiseTick()
diff --git a/Perspex.Base/Threading/MainLoop.cs b/Perspex.Base/Threading/MainLoop.cs
new file mode 100644
index 0000000000..0e1fe67ef2
--- /dev/null
+++ b/Perspex.Base/Threading/MainLoop.cs
@@ -0,0 +1,84 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Win32.Threading
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using NGenerics.DataStructures.Queues;
+ using Perspex.Platform;
+ using Perspex.Threading;
+ using Splat;
+
+ internal class MainLoop
+ {
+ private static IPlatformThreadingInterface platform;
+
+ private PriorityQueue queue =
+ new PriorityQueue(PriorityQueueType.Maximum);
+
+ static MainLoop()
+ {
+ platform = Locator.Current.GetService();
+ }
+
+ public void Run(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ Job job;
+
+ // TODO: Dispatch windows messages in preference to lower priority jobs.
+ while (queue.Count > 0)
+ {
+ lock (this.queue)
+ {
+ job = queue.Dequeue();
+ }
+
+ try
+ {
+ job.Action();
+ job.TaskCompletionSource.SetResult(null);
+ }
+ catch (Exception e)
+ {
+ job.TaskCompletionSource.SetException(e);
+ }
+ }
+
+ platform.ProcessMessage();
+ }
+ }
+
+ public Task InvokeAsync(Action action, DispatcherPriority priority)
+ {
+ var job = new Job(action);
+
+ lock (queue)
+ {
+ this.queue.Add(job, priority);
+ }
+
+ platform.Wake();
+ return job.TaskCompletionSource.Task;
+ }
+
+ private class Job
+ {
+ public Job(Action action)
+ {
+ this.Action = action;
+ this.TaskCompletionSource = new TaskCompletionSource