Procházet zdrojové kódy

Don't use ObserveOn in Bindings.

Instead of using `Observable.ObserveOn` in bindings, interface with `Dispatcher.UIThread` to schedule binding notifications on the UI thread. This saves a significant amount of memory.
Steven Kirk před 8 roky
rodič
revize
4676ac5fb2

+ 28 - 17
src/Avalonia.Base/AvaloniaObject.cs

@@ -325,7 +325,6 @@ namespace Avalonia
             var description = GetDescription(source);
 
             var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
-            source = source.ObserveOn(scheduler); 
 
             if (property.IsDirect)
             {
@@ -674,29 +673,41 @@ namespace Avalonia
         /// <param name="value">The value.</param>
         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);
             }
         }
 

+ 33 - 13
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));
+            }
         }
     }
 }

+ 27 - 0
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<object>();
+            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
+
+            var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
+            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()
         {