Browse Source

New main loop system

pull/213/head
Nikita Tsukanov 11 years ago
parent
commit
d7f3778de6
  1. 4
      samples/TestApplication/TestApplication.csproj
  2. 14
      src/Gtk/Perspex.Gtk/GtkPlatform.cs
  3. 2
      src/Perspex.Application/Application.cs
  4. 2
      src/Perspex.Base/Perspex.Base.csproj
  5. 17
      src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
  6. 35
      src/Perspex.Base/Threading/Dispatcher.cs
  7. 24
      src/Perspex.Base/Threading/DispatcherTimer.cs
  8. 56
      src/Perspex.Base/Threading/JobRunner.cs
  9. 2
      src/Perspex.Base/Threading/PerspexScheduler.cs
  10. 4
      src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
  11. 11
      src/Perspex.Controls/TopLevel.cs
  12. 33
      src/Windows/Perspex.Win32/Win32Platform.cs

4
samples/TestApplication/TestApplication.csproj

@ -84,6 +84,10 @@
<Project>{FB05AC90-89BA-4F2F-A924-F37875FB547C}</Project>
<Name>Perspex.Cairo</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Gtk\Perspex.Gtk\Perspex.Gtk.csproj">
<Project>{54f237d5-a70a-4752-9656-0c70b1a7b047}</Project>
<Name>Perspex.Gtk</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>

14
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;
}
}

2
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);
}
/// <summary>

2
src/Perspex.Base/Perspex.Base.csproj

@ -72,7 +72,7 @@
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />
<Compile Include="Threading\DispatcherTimer.cs" />
<Compile Include="Threading\MainLoop.cs" />
<Compile Include="Threading\JobRunner.cs" />
<Compile Include="Threading\PerspexScheduler.cs" />
<Compile Include="Threading\PerspexSynchronizationContext.cs" />
<Compile Include="Utilities\MathUtilities.cs" />

17
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
/// </summary>
public interface IPlatformThreadingInterface
{
/// <summary>
/// Checks whether there are messages waiting to be processed.
/// </summary>
/// <returns>True if there are messages waiting, otherwise false.</returns>
bool HasMessages();
/// <summary>
/// Process a single message from the windowing system, blocking until one is available.
/// </summary>
void ProcessMessage();
void RunLoop(CancellationToken cancellationToken);
/// <summary>
/// Starts a timer.
@ -32,6 +24,9 @@ namespace Perspex.Platform
/// <summary>
/// Sends a message that causes <see cref="ProcessMessage"/> to exit.
/// </summary>
void Wake();
void Signal();
event Action Signaled;
}
}

35
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 <see cref="Dispatcher"/> in the application -
/// the one for the UI thread, retrieved via the <see cref="UIThread"/> property.
/// </remarks>
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<IPlatformThreadingInterface>());
/// <summary>
/// Initializes a new instance of the <see cref="Dispatcher"/> class.
/// </summary>
private Dispatcher()
static Dispatcher()
{
PerspexLocator.Current.GetService<IPlatformThreadingInterface>().Signaled += _jobRunner.RunJobs;
}
/// <summary>
/// Gets the <see cref="Dispatcher"/> for the UI thread.
/// </summary>
public static Dispatcher UIThread => s_instance;
/// <summary>
/// Runs the dispatcher's main loop.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token used to exit the main loop.
/// </param>
public void MainLoop(CancellationToken cancellationToken)
public static void MainLoop(CancellationToken cancellationToken)
{
_mainLoop.Run(cancellationToken);
var platform = PerspexLocator.Current.GetService<IPlatformThreadingInterface>();
cancellationToken.Register(platform.Signal);
platform.RunLoop(cancellationToken);
}
/// <summary>
/// Runs continuations pushed on the loop.
/// </summary>
public void RunJobs()
public static void RunJobs()
{
_mainLoop.RunJobs();
_jobRunner.RunJobs();
}
/// <summary>
@ -58,9 +55,9 @@ namespace Perspex.Threading
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
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);
}
/// <summary>
@ -68,9 +65,9 @@ namespace Perspex.Threading
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
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);
}
}
}

24
src/Perspex.Base/Threading/DispatcherTimer.cs

@ -24,17 +24,6 @@ namespace Perspex.Threading
public DispatcherTimer()
{
_priority = DispatcherPriority.Normal;
Dispatcher = Dispatcher.UIThread;
}
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary>
/// <param name="priority">The priority to use.</param>
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
Dispatcher = Dispatcher.UIThread;
}
/// <summary>
@ -42,10 +31,9 @@ namespace Perspex.Threading
/// </summary>
/// <param name="priority">The priority to use.</param>
/// <param name="dispatcher">The dispatcher to use.</param>
public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
Dispatcher = dispatcher;
}
/// <summary>
@ -55,10 +43,9 @@ namespace Perspex.Threading
/// <param name="priority">The priority to use.</param>
/// <param name="dispatcher">The dispatcher to use.</param>
/// <param name="callback">The event to call when the timer ticks.</param>
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
/// </summary>
public event EventHandler Tick;
/// <summary>
/// Gets the dispatcher that the timer uses.
/// </summary>
public Dispatcher Dispatcher
{
get; }
/// <summary>
/// Gets or sets the interval at which the timer ticks.
/// </summary>

56
src/Perspex.Base/Threading/MainLoop.cs → 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
{
/// <summary>
/// A main loop in a <see cref="Dispatcher"/>.
/// </summary>
internal class MainLoop
internal class JobRunner
{
private static readonly IPlatformThreadingInterface s_platform;
private readonly IPlatformThreadingInterface _platform;
private readonly Queue<Job> _queue = new Queue<Job>();
/// <summary>
/// Initializes static members of the <see cref="MainLoop"/> class.
/// </summary>
static MainLoop()
public JobRunner(IPlatformThreadingInterface platform)
{
s_platform = PerspexLocator.Current.GetService<IPlatformThreadingInterface>();
}
/// <summary>
/// Runs the main loop.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token used to exit the main loop.
/// </param>
public void Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
RunJobs();
s_platform.ProcessMessage();
}
_platform = platform;
}
/// <summary>
@ -48,21 +27,15 @@ namespace Perspex.Win32.Threading
/// </summary>
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();
}
/// <summary>

2
src/Perspex.Base/Threading/PerspexScheduler.cs

@ -7,7 +7,7 @@ using System.Reactive.Concurrency;
namespace Perspex.Threading
{
/// <summary>
/// A reactive scheduler that uses Perspex's <see cref="Dispatcher.UIThread"/>.
/// A reactive scheduler that uses Perspex's <see cref="Dispatcher"/>.
/// </summary>
public class PerspexScheduler : LocalScheduler
{

4
src/Perspex.Base/Threading/PerspexSynchronizationContext.cs

@ -36,14 +36,14 @@ namespace Perspex.Threading
/// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state)
{
Dispatcher.UIThread.Post(() => d(state));
Dispatcher.Post(() => d(state));
}
/// <inheritdoc/>
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();
}
}
}

11
src/Perspex.Controls/TopLevel.cs

@ -43,11 +43,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<IInputElement> PointerOverElementProperty =
PerspexProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
/// <summary>
/// The dispatcher for the window.
/// </summary>
private readonly Dispatcher _dispatcher;
/// <summary>
/// The render manager for the window.s
/// </summary>
@ -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
/// </summary>
private void HandleLayoutNeeded()
{
_dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
Dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
}
/// <summary>
@ -420,7 +413,7 @@ namespace Perspex.Controls
/// </summary>
private void HandleRenderNeeded()
{
_dispatcher.InvokeAsync(
Dispatcher.InvokeAsync(
() => PlatformImpl.Invalidate(new Rect(ClientSize)),
DispatcherPriority.Render);
}

33
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);
}

Loading…
Cancel
Save