diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8b93cf2fb1..c76b43f04f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -325,7 +325,6 @@ namespace Avalonia var description = GetDescription(source); var scheduler = AvaloniaLocator.Current.GetService() ?? ImmediateScheduler.Instance; - source = source.ObserveOn(scheduler); if (property.IsDirect) { @@ -674,29 +673,41 @@ namespace Avalonia /// The value. private void SetDirectValue(AvaloniaProperty property, object value) { - var notification = value as BindingNotification; - - if (notification != null) + void Set() { - notification.LogIfError(this, property); - value = notification.Value; - } + var notification = value as BindingNotification; - if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue) - { - var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType()); - var accessor = (IDirectPropertyAccessor)GetRegistered(property); - var finalValue = value == AvaloniaProperty.UnsetValue ? - metadata.UnsetValue : value; + if (notification != null) + { + notification.LogIfError(this, property); + value = notification.Value; + } + + if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue) + { + var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType()); + var accessor = (IDirectPropertyAccessor)GetRegistered(property); + var finalValue = value == AvaloniaProperty.UnsetValue ? + metadata.UnsetValue : value; + + LogPropertySet(property, value, BindingPriority.LocalValue); - LogPropertySet(property, value, BindingPriority.LocalValue); + accessor.SetValue(this, finalValue); + } - accessor.SetValue(this, finalValue); + if (notification != null) + { + UpdateDataValidation(property, notification); + } } - if (notification != null) + if (Dispatcher.UIThread.CheckAccess()) + { + Set(); + } + else { - UpdateDataValidation(property, notification); + Dispatcher.UIThread.InvokeAsync(Set); } } diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 63f582a34c..0e293e4fc6 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Data; +using Avalonia.Threading; namespace Avalonia { @@ -92,33 +93,52 @@ namespace Avalonia private void ValueChanged(object value) { - _owner.Owner.Owner?.VerifyAccess(); + void Signal() + { + _owner.Owner.Owner?.VerifyAccess(); - var notification = value as BindingNotification; + var notification = value as BindingNotification; - if (notification != null) - { - if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) + if (notification != null) { - Value = notification.Value; - _owner.Changed(this); + if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) + { + Value = notification.Value; + _owner.Changed(this); + } + + if (notification.ErrorType != BindingErrorType.None) + { + _owner.Error(this, notification); + } } - - if (notification.ErrorType != BindingErrorType.None) + else { - _owner.Error(this, notification); + Value = value; + _owner.Changed(this); } } + + if (Dispatcher.UIThread.CheckAccess()) + { + Signal(); + } else { - Value = value; - _owner.Changed(this); + Dispatcher.UIThread.InvokeAsync(Signal); } } private void Completed() { - _owner.Completed(this); + if (Dispatcher.UIThread.CheckAccess()) + { + _owner.Completed(this); + } + else + { + Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this)); + } } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index e9cb2bf450..b874a1cbc6 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -4,10 +4,15 @@ using System; using System.Collections.Generic; using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; using Avalonia; using Avalonia.Data; using Avalonia.Logging; +using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests @@ -411,6 +416,28 @@ namespace Avalonia.Base.UnitTests Assert.True(called); } + [Fact] + public async Task Bind_Executes_On_UIThread() + { + var target = new Class1(); + var source = new Subject(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + + var services = new TestServices( + threadingInterface: threadingInterfaceMock.Object); + + using (UnitTestApplication.Start(services)) + { + target.Bind(Class1.FooProperty, source); + + await Task.Run(() => source.OnNext("foobar")); + } + } + [Fact] public void AddOwner_Should_Inherit_DefaultBindingMode() {