Browse Source

Implemented scheduling for ExpressionObserver.

Steven Kirk 10 years ago
parent
commit
b252fdaeb1

+ 5 - 3
src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs

@@ -3,11 +3,10 @@
 
 using System;
 using System.ComponentModel;
-using System.Linq;
 using System.Reactive.Linq;
 using System.Reflection;
+using System.Threading;
 using System.Threading.Tasks;
-using Perspex.Threading;
 
 namespace Perspex.Markup.Binding
 {
@@ -93,6 +92,7 @@ namespace Perspex.Markup.Binding
             {
                 CurrentValue = ExpressionValue.None;
                 _subscription = observable
+                    .ObserveOn(SynchronizationContext.Current)
                     .Subscribe(x => CurrentValue = new ExpressionValue(x));
             }
             else if (task != null)
@@ -101,7 +101,9 @@ namespace Perspex.Markup.Binding
 
                 if (resultProperty != null)
                 {
-                    task.ContinueWith(x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task)))                        
+                    task.ContinueWith(
+                            x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task)),
+                            TaskScheduler.FromCurrentSynchronizationContext())
                         .ConfigureAwait(false);
                 }
             }

+ 23 - 16
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Observable.cs

@@ -15,32 +15,39 @@ namespace Perspex.Markup.UnitTests.Binding
         [Fact]
         public void Should_Get_Simple_Observable_Value()
         {
-            var source = new BehaviorSubject<string>("foo");
-            var data = new { Foo = source };
-            var target = new ExpressionObserver(data, "Foo");
-            var result = new List<object>();
+            using (var sync = UnitTestSynchronizationContext.Begin())
+            {
+                var source = new BehaviorSubject<string>("foo");
+                var data = new { Foo = source };
+                var target = new ExpressionObserver(data, "Foo");
+                var result = new List<object>();
 
-            var sub = target.Subscribe(x => result.Add(x.Value));
-            source.OnNext("bar");
+                var sub = target.Subscribe(x => result.Add(x.Value));
+                source.OnNext("bar");
+                sync.ExecutePostedCallbacks();
 
-            Assert.Equal(new[] { "foo", "bar" }, result);
+                Assert.Equal(new[] { null, "foo", "bar" }, result);
+            }
         }
 
         [Fact]
         public void Should_Get_Property_Value_From_Observable()
         {
-            var data = new Class1();
-            var target = new ExpressionObserver(data, "Next.Foo");
-            var result = new List<object>();
-
-            var sub = target.Subscribe(x => result.Add(x.Value));
-            data.Next.OnNext(new Class2("foo"));
+            using (var sync = UnitTestSynchronizationContext.Begin())
+            {
+                var data = new Class1();
+                var target = new ExpressionObserver(data, "Next.Foo");
+                var result = new List<object>();
 
-            Assert.Equal(new[] { null, "foo" }, result);
+                var sub = target.Subscribe(x => result.Add(x.Value));
+                data.Next.OnNext(new Class2("foo"));
+                sync.ExecutePostedCallbacks();
 
-            sub.Dispose();
+                Assert.Equal(new[] { null, "foo" }, result);
 
-            Assert.Equal(0, data.SubscriptionCount);
+                sub.Dispose();
+                Assert.Equal(0, data.SubscriptionCount);
+            }
         }
 
         private class Class1 : NotifyingBase

+ 23 - 14
tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_Task.cs

@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 using Perspex.Markup.Binding;
 using Xunit;
@@ -15,29 +16,37 @@ namespace Perspex.Markup.UnitTests.Binding
         [Fact]
         public void Should_Get_Simple_Task_Value()
         {
-            var tcs = new TaskCompletionSource<string>();
-            var data = new { Foo = tcs.Task };
-            var target = new ExpressionObserver(data, "Foo");
-            var result = new List<object>();
+            using (var sync = UnitTestSynchronizationContext.Begin())
+            {
+                var tcs = new TaskCompletionSource<string>();
+                var data = new { Foo = tcs.Task };
+                var target = new ExpressionObserver(data, "Foo");
+                var result = new List<object>();
 
-            var sub = target.Subscribe(x => result.Add(x.Value));
-            tcs.SetResult("foo");
+                var sub = target.Subscribe(x => result.Add(x.Value));
+                tcs.SetResult("foo");
+                sync.ExecutePostedCallbacks();
 
-            Assert.Equal(new object[] { null, "foo" }, result.ToArray());
+                Assert.Equal(new object[] { null, "foo" }, result.ToArray());
+            }
         }
         
         [Fact]
         public void Should_Get_Property_Value_From_Task()
         {
-            var tcs = new TaskCompletionSource<Class2>();
-            var data = new Class1(tcs.Task);
-            var target = new ExpressionObserver(data, "Next.Foo");
-            var result = new List<object>();
+            using (var sync = UnitTestSynchronizationContext.Begin())
+            {
+                var tcs = new TaskCompletionSource<Class2>();
+                var data = new Class1(tcs.Task);
+                var target = new ExpressionObserver(data, "Next.Foo");
+                var result = new List<object>();
 
-            var sub = target.Subscribe(x => result.Add(x.Value));
-            tcs.SetResult(new Class2("foo"));
+                var sub = target.Subscribe(x => result.Add(x.Value));
+                tcs.SetResult(new Class2("foo"));
+                sync.ExecutePostedCallbacks();
 
-            Assert.Equal(new object[] { null, "foo" }, result.ToArray());
+                Assert.Equal(new object[] { null, "foo" }, result.ToArray());
+            }
         }
 
         private class Class1 : NotifyingBase

+ 1 - 0
tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj

@@ -81,6 +81,7 @@
     <Compile Include="Binding\ExpressionNodeBuilderTests.cs" />
     <Compile Include="Binding\NotifyingBase.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="UnitTestSynchronizationContext.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />

+ 68 - 0
tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs

@@ -0,0 +1,68 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Threading;
+
+namespace Perspex.Markup.UnitTests
+{
+    internal sealed class UnitTestSynchronizationContext : SynchronizationContext
+    {
+        readonly List<Tuple<SendOrPostCallback, object>> _postedCallbacks =
+            new List<Tuple<SendOrPostCallback, object>>();
+
+        public static Scope Begin()
+        {
+            var sync = new UnitTestSynchronizationContext();
+            var old = SynchronizationContext.Current;
+            SynchronizationContext.SetSynchronizationContext(sync);
+            return new Scope(old, sync);
+        }
+
+        public override void Send(SendOrPostCallback d, object state)
+        {
+            d(state);
+        }
+
+        public override void Post(SendOrPostCallback d, object state)
+        {
+            lock (_postedCallbacks)
+            {
+                _postedCallbacks.Add(Tuple.Create(d, state));
+            }
+        }
+
+        public void ExecutePostedCallbacks()
+        {
+            lock (_postedCallbacks)
+            {
+                _postedCallbacks.ForEach(t => t.Item1(t.Item2));
+                _postedCallbacks.Clear();
+            }
+        }
+
+        public class Scope : IDisposable
+        {
+            private SynchronizationContext _old;
+            private UnitTestSynchronizationContext _new;
+
+            public Scope(SynchronizationContext old, UnitTestSynchronizationContext n)
+            {
+                _old = old;
+                _new = n;
+            }
+
+            public void Dispose()
+            {
+                SynchronizationContext.SetSynchronizationContext(_old);
+            }
+
+            public void ExecutePostedCallbacks()
+            {
+                _new.ExecutePostedCallbacks();
+            }
+        }
+    }
+}