diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 1b94ecaa51..d3d8119af8 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -55,7 +55,7 @@ namespace Perspex { throw new InvalidOperationException("Cannot create more than one Application instance."); } - + PerspexSynchronizationContext.InstallIfNeeded(); Current = this; } 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/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 0c0e402c36..ffa0b89f13 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -4,6 +4,8 @@ // // ----------------------------------------------------------------------- +using Perspex.Threading; + namespace Perspex { using System; @@ -100,6 +102,7 @@ namespace Perspex /// public PerspexObject() { + PerspexSynchronizationContext.InstallIfNeeded(); this.propertyLog = Log.ForContext(new[] { new PropertyEnricher("Area", "Property"), diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 31191d587c..e9b91f9ab9 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -68,5 +68,14 @@ namespace Perspex.Threading { return this.mainLoop.InvokeAsync(action, priority); } + + /// + /// Post action that will be invoked on main thread + /// + /// The method. + internal void Post(Action action) + { + this.mainLoop.Post(action); + } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/MainLoop.cs index 009d331b28..b4941118ae 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,27 @@ 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. + internal void Post(Action action) + { + this.AddJob(new Job(action, DispatcherPriority.Normal, 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 +132,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..5c0540ef62 --- /dev/null +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -0,0 +1,47 @@ +using System.Threading; + +namespace Perspex.Threading +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + /// + /// SynchronizationContext to be used on main thread + /// + public class PerspexSynchronizationContext : SynchronizationContext + { + /// + /// Controls if SynchronizationContext should be installed in InstallIfNeeded + /// + 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