diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 5b52ac84d1..276c0b49f3 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -17,6 +17,7 @@ namespace Perspex.Gtk public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings { private static readonly GtkPlatform s_instance = new GtkPlatform(); + private static Thread _uiThread; public GtkPlatform() { @@ -40,6 +41,7 @@ namespace Perspex.Gtk .Bind().ToConstant(s_instance) .Bind().ToSingleton(); SharedPlatform.Register(); + _uiThread = Thread.CurrentThread; } public bool HasMessages() @@ -79,6 +81,8 @@ namespace Perspex.Gtk Gtk.Application.Invoke(delegate { Signaled?.Invoke(); }); } + public bool CheckForLoopThread() => Thread.CurrentThread == _uiThread; + public event Action Signaled; } } \ No newline at end of file diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 696d01893a..27476c881d 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -10,6 +10,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; using Perspex.Reactive; +using Perspex.Threading; using Perspex.Utilities; using Serilog; using Serilog.Core.Enrichers; @@ -300,6 +301,10 @@ namespace Perspex } } + public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); + + public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); + /// /// Clears a 's local value. /// @@ -495,7 +500,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = GetRegistered(property); @@ -556,7 +561,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = (PerspexProperty)GetRegistered(property); @@ -590,7 +595,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = GetRegistered(property); @@ -650,7 +655,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = (PerspexProperty)GetRegistered(property); @@ -687,6 +692,7 @@ namespace Perspex PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue) { + VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, @@ -716,6 +722,7 @@ namespace Perspex ISubject source, BindingPriority priority = BindingPriority.LocalValue) { + VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, @@ -733,6 +740,7 @@ namespace Perspex /// The property. public void Revalidate(PerspexProperty property) { + VerifyAccess(); PriorityValue value; if (_values.TryGetValue(property, out value)) @@ -788,6 +796,7 @@ namespace Perspex BindingPriority priority) { Contract.Requires(property != null); + VerifyAccess(); PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs( this, @@ -824,6 +833,7 @@ namespace Perspex /// protected bool SetAndRaise(PerspexProperty property, ref T field, T value) { + VerifyAccess(); if (!object.Equals(field, value)) { var old = field; diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 04cdd5aaba..897cdc1af4 100644 --- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -26,6 +26,8 @@ namespace Perspex.Platform /// void Signal(); + bool CheckForLoopThread(); + event Action Signaled; } diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 78ec3bdb48..de5024fc0b 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -26,10 +26,21 @@ namespace Perspex.Threading public Dispatcher(IPlatformThreadingInterface platform) { _platform = platform; + if(_platform == null) + //TODO: Unit test mode, fix that somehow + return; _jobRunner = new JobRunner(platform); _platform.Signaled += _jobRunner.RunJobs; } + public bool CheckAccess() => _platform?.CheckForLoopThread() ?? true; + + public void VerifyAccess() + { + if (!CheckAccess()) + throw new InvalidOperationException("Call from invalid thread"); + } + /// /// Runs the dispatcher's main loop. @@ -49,7 +60,7 @@ namespace Perspex.Threading /// public void RunJobs() { - _jobRunner.RunJobs(); + _jobRunner?.RunJobs(); } /// @@ -60,7 +71,7 @@ namespace Perspex.Threading /// A task that can be used to track the method's execution. public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - return _jobRunner.InvokeAsync(action, priority); + return _jobRunner?.InvokeAsync(action, priority); } /// @@ -70,7 +81,7 @@ namespace Perspex.Threading /// The priority with which to invoke the method. public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - _jobRunner.Post(action, priority); + _jobRunner?.Post(action, priority); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs index 7f1ca48593..027c3a4b28 100644 --- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs +++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs @@ -37,6 +37,11 @@ namespace Perspex.Threading () => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); public void Signal() => _evnt.Set(); + public bool CheckForLoopThread() + { + //TODO: Actually perform check + return true; + } public event Action Signaled; } diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 896bbcda65..5a79fadfe0 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -21,6 +21,7 @@ namespace Perspex.Win32 public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings { private static readonly Win32Platform s_instance = new Win32Platform(); + private static Thread _uiThread; private UnmanagedMethods.WndProc _wndProcDelegate; @@ -52,6 +53,7 @@ namespace Perspex.Win32 .Bind().ToSingleton(); SharedPlatform.Register(); + _uiThread = Thread.CurrentThread; } public static void Initialize() @@ -124,6 +126,8 @@ namespace Perspex.Win32 new IntPtr(SignalL)); } + public bool CheckForLoopThread() => _uiThread == Thread.CurrentThread; + public event Action Signaled; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]