diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 5bdf187f71..ac93f82fec 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.MainLoop(source.Token); + Dispatcher.UIThread.MainLoop(source.Token); } /// diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 51e3d0663d..258afdc1b4 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -75,6 +75,7 @@ + diff --git a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs b/src/Perspex.Base/Platform/IPclPlatformWrapper.cs index c57b87a738..20c836bdaf 100644 --- a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs +++ b/src/Perspex.Base/Platform/IPclPlatformWrapper.cs @@ -10,5 +10,7 @@ namespace Perspex.Platform public interface IPclPlatformWrapper { Assembly[] GetLoadedAssemblies(); + void PostThreadPoolItem(Action cb); + IDisposable StartSystemTimer(TimeSpan interval, Action tick); } } diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 29e59bb98e..ee9555552c 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -15,26 +15,29 @@ 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 static class Dispatcher + public class Dispatcher { - private static readonly JobRunner _jobRunner = - new JobRunner(PerspexLocator.Current.GetService()); + private readonly IPlatformThreadingInterface _platform; + private readonly JobRunner _jobRunner; - /// - /// Initializes a new instance of the class. - /// - static Dispatcher() + public static Dispatcher UIThread { get; } = + new Dispatcher(PerspexLocator.Current.GetService()); + + public Dispatcher(IPlatformThreadingInterface platform) { - PerspexLocator.Current.GetService().Signaled += _jobRunner.RunJobs; + _platform = platform; + _jobRunner = new JobRunner(platform); + _platform.Signaled += _jobRunner.RunJobs; } + /// /// Runs the dispatcher's main loop. /// /// /// A cancellation token used to exit the main loop. /// - public static void MainLoop(CancellationToken cancellationToken) + public void MainLoop(CancellationToken cancellationToken) { var platform = PerspexLocator.Current.GetService(); cancellationToken.Register(platform.Signal); @@ -44,7 +47,7 @@ namespace Perspex.Threading /// /// Runs continuations pushed on the loop. /// - public static void RunJobs() + public void RunJobs() { _jobRunner.RunJobs(); } @@ -55,7 +58,7 @@ 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 static Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { return _jobRunner.InvokeAsync(action, priority); } @@ -65,7 +68,7 @@ namespace Perspex.Threading /// /// The method. /// The priority with which to invoke the method. - internal static void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { _jobRunner.Post(action, priority); } diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index bcd228d0c6..688cf57605 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -177,7 +177,7 @@ namespace Perspex.Threading /// private void InternalTick() { - Dispatcher.Post(RaiseTick, _priority); + Dispatcher.UIThread.Post(RaiseTick, _priority); } /// diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index 8ee323e423..ac7b509740 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.Post(() => d(state)); + Dispatcher.UIThread.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.InvokeAsync(() => d(state)).Wait(); + Dispatcher.UIThread.InvokeAsync(() => d(state)).Wait(); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs new file mode 100644 index 0000000000..7f1ca48593 --- /dev/null +++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Perspex.Platform; + +namespace Perspex.Threading +{ + public class SingleThreadDispatcher : Dispatcher + { + class ThreadingInterface : IPlatformThreadingInterface + { + private AutoResetEvent _evnt = new AutoResetEvent(false); + private JobRunner _timerJobRunner; + + public ThreadingInterface() + { + _timerJobRunner = new JobRunner(this); + } + + public void RunLoop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + _evnt.WaitOne(); + if (cancellationToken.IsCancellationRequested) + return; + Signaled?.Invoke(); + _timerJobRunner.RunJobs(); + } + } + + public IDisposable StartTimer(TimeSpan interval, Action tick) + => PerspexLocator.Current.GetService().StartSystemTimer(interval, + () => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); + + public void Signal() => _evnt.Set(); + + public event Action Signaled; + } + + public SingleThreadDispatcher() : base(new ThreadingInterface()) + { + } + + public static Dispatcher StartNew(CancellationToken token) + { + var dispatcher = new SingleThreadDispatcher(); + PerspexLocator.Current.GetService().PostThreadPoolItem(() => + { + dispatcher.MainLoop(token); + }); + return dispatcher; + } + } +} diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index da59235dd6..d6e3c37a75 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -397,7 +397,7 @@ namespace Perspex.Controls /// private void HandleLayoutNeeded() { - Dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.UIThread.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// @@ -413,7 +413,7 @@ namespace Perspex.Controls /// private void HandleRenderNeeded() { - Dispatcher.InvokeAsync( + Dispatcher.UIThread.InvokeAsync( () => PlatformImpl.Invalidate(new Rect(ClientSize)), DispatcherPriority.Render); } diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index f5e52a5fed..55037f9a2f 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -563,7 +563,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnLoadComplete(e); else - Dispatcher.Invoke(new Action(OnLinkClicked), e); + Dispatcher.UIThread.Invoke(new Action(OnLinkClicked), e); } @@ -572,7 +572,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnLinkClicked(e); else - Dispatcher.Invoke(new Action(OnLinkClicked), e); + Dispatcher.UIThread.Invoke(new Action(OnLinkClicked), e); } private void OnRenderError(object sender, HtmlRenderErrorEventArgs e) @@ -580,7 +580,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnRenderError(e); else - Dispatcher.Invoke(new Action(OnRenderError), e); + Dispatcher.UIThread.Invoke(new Action(OnRenderError), e); } private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e) @@ -588,7 +588,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnStylesheetLoad(e); else - Dispatcher.Invoke(new Action(OnStylesheetLoad), e); + Dispatcher.UIThread.Invoke(new Action(OnStylesheetLoad), e); } private void OnImageLoad(object sender, HtmlImageLoadEventArgs e) @@ -596,7 +596,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnImageLoad(e); else - Dispatcher.Invoke(new Action(OnImageLoad), e); + Dispatcher.UIThread.Invoke(new Action(OnImageLoad), e); } private void OnRefresh(object sender, HtmlRefreshEventArgs e) @@ -604,7 +604,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnRefresh(e); else - Dispatcher.Invoke(new Action(OnRefresh), e); + Dispatcher.UIThread.Invoke(new Action(OnRefresh), e); } */ } diff --git a/src/Shared/PlatformSupport/PclPlatformWrapper.cs b/src/Shared/PlatformSupport/PclPlatformWrapper.cs index c5a4b3a320..7ef2839354 100644 Binary files a/src/Shared/PlatformSupport/PclPlatformWrapper.cs and b/src/Shared/PlatformSupport/PclPlatformWrapper.cs differ