diff --git a/samples/TestApplication/TestApplication.csproj b/samples/TestApplication/TestApplication.csproj
index b56417d523..32b23242ac 100644
--- a/samples/TestApplication/TestApplication.csproj
+++ b/samples/TestApplication/TestApplication.csproj
@@ -84,6 +84,10 @@
{FB05AC90-89BA-4F2F-A924-F37875FB547C}
Perspex.Cairo
+
+ {54f237d5-a70a-4752-9656-0c70b1a7b047}
+ Perspex.Gtk
+
{D211E587-D8BC-45B9-95A4-F297C8FA5200}
Perspex.Animation
diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
index e6c2db7c9c..5b52ac84d1 100644
--- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs
+++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs
@@ -3,6 +3,7 @@
using System;
using System.Reactive.Disposables;
+using System.Threading;
using Perspex.Controls.Platform;
using Perspex.Input.Platform;
using Perspex.Input;
@@ -51,6 +52,12 @@ namespace Perspex.Gtk
Gtk.Application.RunIteration();
}
+ public void RunLoop(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ Gtk.Application.RunIteration();
+ }
+
public IDisposable StartTimer(TimeSpan interval, Action tick)
{
var result = true;
@@ -65,8 +72,13 @@ namespace Perspex.Gtk
return Disposable.Create(() => result = false);
}
- public void Wake()
+
+
+ public void Signal()
{
+ Gtk.Application.Invoke(delegate { Signaled?.Invoke(); });
}
+
+ public event Action Signaled;
}
}
\ No newline at end of file
diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs
index ac93f82fec..5bdf187f71 100644
--- a/src/Perspex.Application/Application.cs
+++ b/src/Perspex.Application/Application.cs
@@ -144,7 +144,7 @@ namespace Perspex
{
var source = new CancellationTokenSource();
closable.Closed += (s, e) => source.Cancel();
- Dispatcher.UIThread.MainLoop(source.Token);
+ Dispatcher.MainLoop(source.Token);
}
///
diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj
index d90087add4..51e3d0663d 100644
--- a/src/Perspex.Base/Perspex.Base.csproj
+++ b/src/Perspex.Base/Perspex.Base.csproj
@@ -72,7 +72,7 @@
-
+
diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
index 2410cf5a28..04cdd5aaba 100644
--- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
+++ b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Threading;
namespace Perspex.Platform
{
@@ -10,16 +11,7 @@ namespace Perspex.Platform
///
public interface IPlatformThreadingInterface
{
- ///
- /// Checks whether there are messages waiting to be processed.
- ///
- /// True if there are messages waiting, otherwise false.
- bool HasMessages();
-
- ///
- /// Process a single message from the windowing system, blocking until one is available.
- ///
- void ProcessMessage();
+ void RunLoop(CancellationToken cancellationToken);
///
/// Starts a timer.
@@ -32,6 +24,9 @@ namespace Perspex.Platform
///
/// Sends a message that causes to exit.
///
- void Wake();
+ void Signal();
+
+ event Action Signaled;
+
}
}
diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs
index 629492c2d0..29e59bb98e 100644
--- a/src/Perspex.Base/Threading/Dispatcher.cs
+++ b/src/Perspex.Base/Threading/Dispatcher.cs
@@ -4,7 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using Perspex.Win32.Threading;
+using Perspex.Platform;
namespace Perspex.Threading
{
@@ -15,41 +15,38 @@ namespace Perspex.Threading
/// In Perspex, there is usually only a single in the application -
/// the one for the UI thread, retrieved via the property.
///
- public class Dispatcher
+ public static class Dispatcher
{
- private static readonly Dispatcher s_instance = new Dispatcher();
-
- private readonly MainLoop _mainLoop = new MainLoop();
+ private static readonly JobRunner _jobRunner =
+ new JobRunner(PerspexLocator.Current.GetService());
///
/// Initializes a new instance of the class.
///
- private Dispatcher()
+ static Dispatcher()
{
+ PerspexLocator.Current.GetService().Signaled += _jobRunner.RunJobs;
}
- ///
- /// Gets the for the UI thread.
- ///
- public static Dispatcher UIThread => s_instance;
-
///
/// Runs the dispatcher's main loop.
///
///
/// A cancellation token used to exit the main loop.
///
- public void MainLoop(CancellationToken cancellationToken)
+ public static void MainLoop(CancellationToken cancellationToken)
{
- _mainLoop.Run(cancellationToken);
+ var platform = PerspexLocator.Current.GetService();
+ cancellationToken.Register(platform.Signal);
+ platform.RunLoop(cancellationToken);
}
///
/// Runs continuations pushed on the loop.
///
- public void RunJobs()
+ public static void RunJobs()
{
- _mainLoop.RunJobs();
+ _jobRunner.RunJobs();
}
///
@@ -58,9 +55,9 @@ namespace Perspex.Threading
/// The method.
/// The priority with which to invoke the method.
/// A task that can be used to track the method's execution.
- public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+ public static Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
- return _mainLoop.InvokeAsync(action, priority);
+ return _jobRunner.InvokeAsync(action, priority);
}
///
@@ -68,9 +65,9 @@ namespace Perspex.Threading
///
/// The method.
/// The priority with which to invoke the method.
- internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+ internal static void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
- _mainLoop.Post(action, priority);
+ _jobRunner.Post(action, priority);
}
}
}
\ No newline at end of file
diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs
index 27b1a8cb36..bcd228d0c6 100644
--- a/src/Perspex.Base/Threading/DispatcherTimer.cs
+++ b/src/Perspex.Base/Threading/DispatcherTimer.cs
@@ -24,17 +24,6 @@ namespace Perspex.Threading
public DispatcherTimer()
{
_priority = DispatcherPriority.Normal;
- Dispatcher = Dispatcher.UIThread;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The priority to use.
- public DispatcherTimer(DispatcherPriority priority)
- {
- _priority = priority;
- Dispatcher = Dispatcher.UIThread;
}
///
@@ -42,10 +31,9 @@ namespace Perspex.Threading
///
/// The priority to use.
/// The dispatcher to use.
- public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
+ public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
- Dispatcher = dispatcher;
}
///
@@ -55,10 +43,9 @@ namespace Perspex.Threading
/// The priority to use.
/// The dispatcher to use.
/// The event to call when the timer ticks.
- public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher)
+ public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback)
{
_priority = priority;
- Dispatcher = dispatcher;
Interval = interval;
Tick += callback;
}
@@ -79,13 +66,6 @@ namespace Perspex.Threading
///
public event EventHandler Tick;
- ///
- /// Gets the dispatcher that the timer uses.
- ///
- public Dispatcher Dispatcher
- {
- get; }
-
///
/// Gets or sets the interval at which the timer ticks.
///
diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/JobRunner.cs
similarity index 73%
rename from src/Perspex.Base/Threading/MainLoop.cs
rename to src/Perspex.Base/Threading/JobRunner.cs
index b9d21d5ab5..67d5301224 100644
--- a/src/Perspex.Base/Threading/MainLoop.cs
+++ b/src/Perspex.Base/Threading/JobRunner.cs
@@ -6,41 +6,20 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Perspex.Platform;
-using Perspex.Threading;
-namespace Perspex.Win32.Threading
+namespace Perspex.Threading
{
///
/// A main loop in a .
///
- internal class MainLoop
+ internal class JobRunner
{
- private static readonly IPlatformThreadingInterface s_platform;
-
+ private readonly IPlatformThreadingInterface _platform;
private readonly Queue _queue = new Queue();
- ///
- /// Initializes static members of the class.
- ///
- static MainLoop()
+ public JobRunner(IPlatformThreadingInterface platform)
{
- s_platform = PerspexLocator.Current.GetService();
- }
-
- ///
- /// Runs the main loop.
- ///
- ///
- /// A cancellation token used to exit the main loop.
- ///
- public void Run(CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- RunJobs();
-
- s_platform.ProcessMessage();
- }
+ _platform = platform;
}
///
@@ -48,21 +27,15 @@ namespace Perspex.Win32.Threading
///
public void RunJobs()
{
- Job job = null;
-
- while (job != null || _queue.Count > 0)
+ while (true)
{
- if (job == null)
- {
- lock (_queue)
- {
- job = _queue.Dequeue();
- }
- }
+ Job job;
- if (job.Priority < DispatcherPriority.Input && s_platform.HasMessages())
+ lock (_queue)
{
- break;
+ if (_queue.Count == 0)
+ return;
+ job = _queue.Dequeue();
}
if (job.TaskCompletionSource == null)
@@ -81,8 +54,6 @@ namespace Perspex.Win32.Threading
job.TaskCompletionSource.SetException(e);
}
}
-
- job = null;
}
}
@@ -113,11 +84,14 @@ namespace Perspex.Win32.Threading
private void AddJob(Job job)
{
+ var needWake = false;
lock (_queue)
{
+ needWake = _queue.Count == 0;
_queue.Enqueue(job);
}
- s_platform.Wake();
+ if (needWake)
+ _platform.Signal();
}
///
diff --git a/src/Perspex.Base/Threading/PerspexScheduler.cs b/src/Perspex.Base/Threading/PerspexScheduler.cs
index 568baf12cb..4861600c53 100644
--- a/src/Perspex.Base/Threading/PerspexScheduler.cs
+++ b/src/Perspex.Base/Threading/PerspexScheduler.cs
@@ -7,7 +7,7 @@ using System.Reactive.Concurrency;
namespace Perspex.Threading
{
///
- /// A reactive scheduler that uses Perspex's .
+ /// A reactive scheduler that uses Perspex's .
///
public class PerspexScheduler : LocalScheduler
{
diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
index c1922b6f28..8ee323e423 100644
--- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
+++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
@@ -36,14 +36,14 @@ namespace Perspex.Threading
///
public override void Post(SendOrPostCallback d, object state)
{
- Dispatcher.UIThread.Post(() => d(state));
+ Dispatcher.Post(() => d(state));
}
///
public override void Send(SendOrPostCallback d, object state)
{
// TODO: Add check for being on the main thread, we should invoke the method immediately in this case
- Dispatcher.UIThread.InvokeAsync(() => d(state)).Wait();
+ Dispatcher.InvokeAsync(() => d(state)).Wait();
}
}
}
\ No newline at end of file
diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs
index 33a7eca9a2..da59235dd6 100644
--- a/src/Perspex.Controls/TopLevel.cs
+++ b/src/Perspex.Controls/TopLevel.cs
@@ -43,11 +43,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty PointerOverElementProperty =
PerspexProperty.Register(nameof(IInputRoot.PointerOverElement));
- ///
- /// The dispatcher for the window.
- ///
- private readonly Dispatcher _dispatcher;
-
///
/// The render manager for the window.s
///
@@ -126,8 +121,6 @@ namespace Perspex.Controls
Size clientSize = ClientSize = PlatformImpl.ClientSize;
- _dispatcher = Dispatcher.UIThread;
-
if (renderInterface != null)
{
_renderer = renderInterface.CreateRenderer(PlatformImpl.Handle, clientSize.Width, clientSize.Height);
@@ -404,7 +397,7 @@ namespace Perspex.Controls
///
private void HandleLayoutNeeded()
{
- _dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
+ Dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
}
///
@@ -420,7 +413,7 @@ namespace Perspex.Controls
///
private void HandleRenderNeeded()
{
- _dispatcher.InvokeAsync(
+ Dispatcher.InvokeAsync(
() => PlatformImpl.Invalidate(new Rect(ClientSize)),
DispatcherPriority.Render);
}
diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs
index c513fbf97e..896bbcda65 100644
--- a/src/Windows/Perspex.Win32/Win32Platform.cs
+++ b/src/Windows/Perspex.Win32/Win32Platform.cs
@@ -8,6 +8,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
+using System.Threading;
using Perspex.Controls.Platform;
using Perspex.Input;
using Perspex.Platform;
@@ -79,6 +80,17 @@ namespace Perspex.Win32
UnmanagedMethods.DispatchMessage(ref msg);
}
+ public void RunLoop(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ UnmanagedMethods.MSG msg;
+ UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0);
+ UnmanagedMethods.TranslateMessage(ref msg);
+ UnmanagedMethods.DispatchMessage(ref msg);
+ }
+ }
+
public IDisposable StartTimer(TimeSpan interval, Action callback)
{
UnmanagedMethods.TimerProc timerDelegate =
@@ -100,18 +112,27 @@ namespace Perspex.Win32
});
}
- public void Wake()
+ private static readonly int SignalW = unchecked((int) 0xdeadbeaf);
+ private static readonly int SignalL = unchecked((int)0x12345678);
+
+ public void Signal()
{
- //UnmanagedMethods.PostMessage(
- // this.hwnd,
- // (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
- // IntPtr.Zero,
- // IntPtr.Zero);
+ UnmanagedMethods.PostMessage(
+ _hwnd,
+ (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
+ new IntPtr(SignalW),
+ new IntPtr(SignalL));
}
+ public event Action Signaled;
+
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
+ if (msg == (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL)
+ {
+ Signaled?.Invoke();
+ }
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}