diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 1b94ecaa51..40c3542d8c 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -150,6 +150,7 @@ namespace Perspex /// protected virtual void RegisterServices() { + PerspexSynchronizationContext.InstallIfNeeded(); this.FocusManager = new FocusManager(); this.InputManager = new InputManager(); diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 654fbb3926..d5723e5a6d 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -72,6 +72,7 @@ + diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 31191d587c..d959151543 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -68,5 +68,15 @@ namespace Perspex.Threading { return this.mainLoop.InvokeAsync(action, priority); } + + /// + /// Post action that will be invoked on main thread + /// + /// The method. + /// The priority with which to invoke the method. + internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + this.mainLoop.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 73335ea408..0d49bb21f8 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -203,7 +203,7 @@ namespace Perspex.Threading /// private void InternalTick() { - this.Dispatcher.InvokeAsync(this.RaiseTick, this.priority); + this.Dispatcher.Post(this.RaiseTick, this.priority); } /// diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/MainLoop.cs index 009d331b28..3a99133d15 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/src/Perspex.Base/Threading/MainLoop.cs @@ -70,14 +70,21 @@ namespace Perspex.Win32.Threading break; } - try + if (job.TaskCompletionSource == null) { job.Action(); - job.TaskCompletionSource.SetResult(null); } - catch (Exception e) + else { - job.TaskCompletionSource.SetException(e); + try + { + job.Action(); + job.TaskCompletionSource.SetResult(null); + } + catch (Exception e) + { + job.TaskCompletionSource.SetException(e); + } } job = null; @@ -92,15 +99,29 @@ namespace Perspex.Win32.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Action action, DispatcherPriority priority) { - var job = new Job(action, priority); + var job = new Job(action, priority, false); + this.AddJob(job); + return job.TaskCompletionSource.Task; + } + + /// + /// Post action that will be invoked on main thread + /// + /// The method. + /// + /// The priority with which to invoke the method. + internal void Post(Action action, DispatcherPriority priority) + { + this.AddJob(new Job(action, priority, true)); + } + private void AddJob(Job job) + { lock (this.queue) { - this.queue.Add(job, priority); + this.queue.Add(job, job.Priority); } - platform.Wake(); - return job.TaskCompletionSource.Task; } /// @@ -113,11 +134,12 @@ namespace Perspex.Win32.Threading /// /// The method to call. /// The job priority. - public Job(Action action, DispatcherPriority priority) + /// Do not wrap excepption in TaskCompletionSource + public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) { this.Action = action; this.Priority = priority; - this.TaskCompletionSource = new TaskCompletionSource(); + this.TaskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } /// diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs new file mode 100644 index 0000000000..40bbfc801c --- /dev/null +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -0,0 +1,47 @@ +namespace Perspex.Threading +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Threading; + + + /// + /// SynchronizationContext to be used on main thread + /// + public class PerspexSynchronizationContext : SynchronizationContext + { + /// + /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. + /// + public static bool AutoInstall { get; set; } = true; + + /// + /// Installs synchronization context in current thread + /// + public static void InstallIfNeeded() + { + if (!AutoInstall || Current is PerspexSynchronizationContext) + { + return; + } + + SetSynchronizationContext(new PerspexSynchronizationContext()); + } + + /// + public override void Post(SendOrPostCallback d, object 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.UIThread.InvokeAsync(() => d(state)).Wait(); + } + } +} \ No newline at end of file diff --git a/src/Perspex.ReactiveUI/Registrations.cs b/src/Perspex.ReactiveUI/Registrations.cs index b6b09fb7f2..699d02d87f 100644 --- a/src/Perspex.ReactiveUI/Registrations.cs +++ b/src/Perspex.ReactiveUI/Registrations.cs @@ -1,4 +1,6 @@ using System; +using System.Reactive.Concurrency; +using System.Threading; namespace ReactiveUI @@ -12,7 +14,7 @@ namespace ReactiveUI { public void Register(Action, Type> registerFunction) { - + RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current); } } }