Просмотр исходного кода

Address flaky UI framework tests.

Bart De Smet 5 лет назад
Родитель
Сommit
6b76145ef7

+ 36 - 8
Rx.NET/Source/tests/Tests.System.Reactive/DispatcherHelpers.cs

@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT License.
 // See the LICENSE file in the project root for more information. 
 
+using System.Reactive.Disposables;
+
 #if NETCOREAPP2_1 || NET472 || NETCOREAPP3_1 || CSWINRT
 using System.Threading;
 #endif
@@ -15,8 +17,12 @@ namespace ReactiveTests
 #if HAS_DISPATCHER
     static class DispatcherHelpers
     {
-        public static DispatcherWrapper EnsureDispatcher()
+        private static readonly Semaphore s_oneDispatcher = new Semaphore(1, 1);
+
+        public static IDisposable RunTest(out DispatcherWrapper wrapper)
         {
+            s_oneDispatcher.WaitOne();
+
 #if DESKTOPCLR
             var dispatcher = new Thread(Dispatcher.Run);
             dispatcher.IsBackground = true;
@@ -29,11 +35,38 @@ namespace ReactiveTests
 
             while (d.BeginInvoke(new Action(() => { })).Status == DispatcherOperationStatus.Aborted) ;
 
-            return new DispatcherWrapper(d);
+            wrapper = new DispatcherWrapper(d);
+
+            return new DispatcherTest(dispatcher);
 #else
-            return new DispatcherWrapper(Dispatcher.CurrentDispatcher);
+            wrapper = new DispatcherWrapper(Dispatcher.CurrentDispatcher);
+
+            return Disposable.Empty; // REVIEW: Anything to shut down?
 #endif
         }
+
+#if DESKTOPCLR
+        private sealed class DispatcherTest : IDisposable
+        {
+            private readonly Thread _t;
+
+            public DispatcherTest(Thread t)
+            {
+                _t = t;
+            }
+
+            public void Dispose()
+            {
+                var d = Dispatcher.FromThread(_t);
+
+                d.BeginInvoke(new Action(() => d.InvokeShutdown()));
+
+                _t.Join();
+
+                s_oneDispatcher.Release();
+            }
+        }
+#endif
     }
 
     class DispatcherWrapper
@@ -47,11 +80,6 @@ namespace ReactiveTests
 
         public Dispatcher Dispatcher { get { return _dispatcher; } }
 
-        public void InvokeShutdown()
-        {
-            _dispatcher.InvokeShutdown();
-        }
-
         public static implicit operator Dispatcher(DispatcherWrapper wrapper)
         {
             return wrapper._dispatcher;

+ 123 - 109
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Concurrency/DispatcherSchedulerTest.cs

@@ -26,16 +26,18 @@ namespace ReactiveTests.Tests
         [Fact]
         public void Current()
         {
-            var d = DispatcherHelpers.EnsureDispatcher();
-            var e = new ManualResetEvent(false);
-
-            d.BeginInvoke(() =>
+            using (DispatcherHelpers.RunTest(out var d))
             {
-                var c = DispatcherScheduler.Current;
-                c.Schedule(() => { e.Set(); });
-            });
+                var e = new ManualResetEvent(false);
+
+                d.BeginInvoke(() =>
+                {
+                    var c = DispatcherScheduler.Current;
+                    c.Schedule(() => { e.Set(); });
+                });
 
-            e.WaitOne();
+                e.WaitOne();
+            }
         }
 
         [Fact]
@@ -64,69 +66,76 @@ namespace ReactiveTests.Tests
         [Fact]
         public void Dispatcher()
         {
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            Assert.Same(disp.Dispatcher, new DispatcherScheduler(disp).Dispatcher);
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                Assert.Same(disp.Dispatcher, new DispatcherScheduler(disp).Dispatcher);
+            }
         }
 
         [Fact]
         public void Now()
         {
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var res = new DispatcherScheduler(disp).Now - DateTime.Now;
-            Assert.True(res.Seconds < 1);
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var res = new DispatcherScheduler(disp).Now - DateTime.Now;
+                Assert.True(res.Seconds < 1);
+            }
         }
 
         [Fact]
         public void Schedule_ArgumentChecking()
         {
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var s = new DispatcherScheduler(disp);
-            ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, default(Func<IScheduler, int, IDisposable>)));
-            ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, TimeSpan.FromSeconds(1), default(Func<IScheduler, int, IDisposable>)));
-            ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, DateTimeOffset.Now, default(Func<IScheduler, int, IDisposable>)));
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var s = new DispatcherScheduler(disp);
+                ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, default(Func<IScheduler, int, IDisposable>)));
+                ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, TimeSpan.FromSeconds(1), default(Func<IScheduler, int, IDisposable>)));
+                ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, DateTimeOffset.Now, default(Func<IScheduler, int, IDisposable>)));
+            }
         }
 
         [Fact]
         [Asynchronous]
         public void Schedule()
         {
-            var disp = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var disp))
             {
-                var id = Thread.CurrentThread.ManagedThreadId;
-                var sch = new DispatcherScheduler(disp);
-                sch.Schedule(() =>
+                RunAsync(evt =>
                 {
-                    Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
-                    disp.InvokeShutdown();
-                    evt.Set();
+                    var id = Thread.CurrentThread.ManagedThreadId;
+                    var sch = new DispatcherScheduler(disp);
+                    sch.Schedule(() =>
+                    {
+                        Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                        evt.Set();
+                    });
                 });
-            });
+            }
         }
 
         [Fact]
         public void ScheduleError()
         {
-            var ex = new Exception();
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var ex = new Exception();
 
-            var id = Thread.CurrentThread.ManagedThreadId;
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var evt = new ManualResetEvent(false);
+                var id = Thread.CurrentThread.ManagedThreadId;
+                var evt = new ManualResetEvent(false);
 
-            Exception thrownEx = null;
-            disp.UnhandledException += (o, e) =>
-            {
-                thrownEx = e.Exception;
-                evt.Set();
-                e.Handled = true;
-            };
-            var sch = new DispatcherScheduler(disp);
-            sch.Schedule(() => { throw ex; });
-            evt.WaitOne();
-            disp.InvokeShutdown();
-
-            Assert.Same(ex, thrownEx);
+                Exception thrownEx = null;
+                disp.UnhandledException += (o, e) =>
+                {
+                    thrownEx = e.Exception;
+                    evt.Set();
+                    e.Handled = true;
+                };
+                var sch = new DispatcherScheduler(disp);
+                sch.Schedule(() => { throw ex; });
+                evt.WaitOne();
+
+                Assert.Same(ex, thrownEx);
+            }
         }
 
         [Fact]
@@ -143,111 +152,116 @@ namespace ReactiveTests.Tests
 
         private void ScheduleRelative_(TimeSpan delay)
         {
-            var evt = new ManualResetEvent(false);
-
-            var id = Thread.CurrentThread.ManagedThreadId;
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var evt = new ManualResetEvent(false);
 
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var sch = new DispatcherScheduler(disp);
+                var id = Thread.CurrentThread.ManagedThreadId;
 
-            sch.Schedule(delay, () =>
-            {
-                Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                var sch = new DispatcherScheduler(disp);
 
                 sch.Schedule(delay, () =>
                 {
                     Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
-                    evt.Set();
+
+                    sch.Schedule(delay, () =>
+                    {
+                        Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                        evt.Set();
+                    });
                 });
-            });
 
-            evt.WaitOne();
-            disp.InvokeShutdown();
+                evt.WaitOne();
+            }
         }
 
         [Fact]
         public void ScheduleRelative_Cancel()
         {
-            var evt = new ManualResetEvent(false);
-            
-            var id = Thread.CurrentThread.ManagedThreadId;
-
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var sch = new DispatcherScheduler(disp);
-            
-            sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
+            using (DispatcherHelpers.RunTest(out var disp))
             {
-                Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                var evt = new ManualResetEvent(false);
+                
+                var id = Thread.CurrentThread.ManagedThreadId;
 
-                var d = sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
+                var sch = new DispatcherScheduler(disp);
+                
+                sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
                 {
-                    Assert.True(false);
-                    evt.Set();
-                });
+                    Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
 
-                sch.Schedule(() =>
-                {
-                    d.Dispose();
-                });
+                    var d = sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
+                    {
+                        Assert.True(false);
+                        evt.Set();
+                    });
 
-                sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
-                {
-                    Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
-                    evt.Set();
+                    sch.Schedule(() =>
+                    {
+                        d.Dispose();
+                    });
+
+                    sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
+                    {
+                        Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                        evt.Set();
+                    });
                 });
-            });
 
-            evt.WaitOne();
-            disp.InvokeShutdown();
+                evt.WaitOne();
+            }
         }
 
         [Fact]
         public void SchedulePeriodic_ArgumentChecking()
         {
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var s = new DispatcherScheduler(disp);
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var s = new DispatcherScheduler(disp);
 
-            ReactiveAssert.Throws<ArgumentNullException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(1), default(Func<int, int>)));
-            ReactiveAssert.Throws<ArgumentOutOfRangeException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(-1), x => x));
+                ReactiveAssert.Throws<ArgumentNullException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(1), default(Func<int, int>)));
+                ReactiveAssert.Throws<ArgumentOutOfRangeException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(-1), x => x));
+            }
         }
 
         [Fact]
         public void SchedulePeriodic()
         {
-            var evt = new ManualResetEvent(false);
-
-            var id = Thread.CurrentThread.ManagedThreadId;
+            using (DispatcherHelpers.RunTest(out var disp))
+            {
+                var evt = new ManualResetEvent(false);
 
-            var disp = DispatcherHelpers.EnsureDispatcher();
-            var sch = new DispatcherScheduler(disp);
+                var id = Thread.CurrentThread.ManagedThreadId;
 
-            var d = new SingleAssignmentDisposable();
+                var sch = new DispatcherScheduler(disp);
 
-            d.Disposable = sch.SchedulePeriodic(1, TimeSpan.FromSeconds(0.1), n =>
-            {
-                Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                var d = new SingleAssignmentDisposable();
 
-                if (n == 3)
+                d.Disposable = sch.SchedulePeriodic(1, TimeSpan.FromSeconds(0.1), n =>
                 {
-                    d.Dispose();
+                    Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
 
-                    sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
+                    if (n == 3)
                     {
-                        Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
-                        evt.Set();
-                    });
-                }
+                        d.Dispose();
 
-                if (n > 3)
-                {
-                    Assert.True(false);
-                }
+                        sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
+                        {
+                            Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
+                            evt.Set();
+                        });
+                    }
 
-                return n + 1;
-            });
+                    if (n > 3)
+                    {
+                        Assert.True(false);
+                    }
+
+                    return n + 1;
+                });
 
-            evt.WaitOne();
-            disp.InvokeShutdown();
+                evt.WaitOne();
+            }
         }
     }
 }

+ 87 - 104
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/ObserveOnTest.cs

@@ -57,59 +57,43 @@ namespace ReactiveTests.Tests
         [Fact]
         public void ObserveOn_Control()
         {
-            var lbl = CreateLabel();
-
-            var evt = new ManualResetEvent(false);
             bool okay = true;
-            Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(lbl).Subscribe(x =>
+
+            using (WinFormsTestUtils.RunTest(out var lbl))
             {
-                lbl.Text = x.ToString();
-                okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-            }, () => evt.Set());
+                var evt = new ManualResetEvent(false);
+
+                Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(lbl).Subscribe(x =>
+                {
+                    lbl.Text = x.ToString();
+                    okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
+                }, () => evt.Set());
+
+                evt.WaitOne();
+            }
 
-            evt.WaitOne();
-            Application.Exit();
             Assert.True(okay);
         }
 
         [Fact]
         public void ObserveOn_ControlScheduler()
         {
-            var lbl = CreateLabel();
-
-            var evt = new ManualResetEvent(false);
             bool okay = true;
-            Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(new ControlScheduler(lbl)).Subscribe(x =>
-            {
-                lbl.Text = x.ToString();
-                okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-            }, () => evt.Set());
-
-            evt.WaitOne();
-            Application.Exit();
-            Assert.True(okay);
-        }
 
-        private Label CreateLabel()
-        {
-            var loaded = new ManualResetEvent(false);
-            var lbl = default(Label);
-
-            var t = new Thread(() =>
+            using (WinFormsTestUtils.RunTest(out var lbl))
             {
-                lbl = new Label();
-                var frm = new Form { Controls = { lbl }, Width = 0, Height = 0, FormBorderStyle = FormBorderStyle.None, ShowInTaskbar = false };
-                frm.Load += (_, __) =>
+                var evt = new ManualResetEvent(false);
+                
+                Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(new ControlScheduler(lbl)).Subscribe(x =>
                 {
-                    loaded.Set();
-                };
-                Application.Run(frm);
-            });
-            t.SetApartmentState(ApartmentState.STA);
-            t.Start();
-
-            loaded.WaitOne();
-            return lbl;
+                    lbl.Text = x.ToString();
+                    okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
+                }, () => evt.Set());
+
+                evt.WaitOne();
+            }
+
+            Assert.True(okay);
         }
 #endif
 #if HAS_DISPATCHER
@@ -117,100 +101,99 @@ namespace ReactiveTests.Tests
         [Asynchronous]
         public void ObserveOn_Dispatcher()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                bool okay = true;
-                Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(dispatcher).Subscribe(x =>
+                RunAsync(evt =>
                 {
-                    okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                }, () =>
-                {
-                    Assert.True(okay);
-                    dispatcher.InvokeShutdown();
-                    evt.Set();
+                    bool okay = true;
+                    Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(dispatcher).Subscribe(x =>
+                    {
+                        okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                    }, () =>
+                    {
+                        Assert.True(okay);
+                        evt.Set();
+                    });
                 });
-            });
+            }
         }
 
         [Fact]
         [Asynchronous]
         public void ObserveOn_DispatcherScheduler()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                bool okay = true;
-                Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(new DispatcherScheduler(dispatcher)).Subscribe(x =>
-                {
-                    okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                }, () =>
+                RunAsync(evt =>
                 {
-                    Assert.True(okay);
-                    dispatcher.InvokeShutdown();
-                    evt.Set();
+                    bool okay = true;
+                    Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOn(new DispatcherScheduler(dispatcher)).Subscribe(x =>
+                    {
+                        okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                    }, () =>
+                    {
+                        Assert.True(okay);
+                        evt.Set();
+                    });
                 });
-            });
+            }
         }
 
         [Fact]
         [Asynchronous]
         public void ObserveOn_CurrentDispatcher()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                bool okay = true;
-                dispatcher.BeginInvoke(new Action(() =>
+                RunAsync(evt =>
                 {
-                    Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOnDispatcher().Subscribe(x =>
-                    {
-                        okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                    },  () =>
+                    bool okay = true;
+                    dispatcher.BeginInvoke(new Action(() =>
                     {
-                        Assert.True(okay);
-                        dispatcher.InvokeShutdown();
-                        evt.Set();
-                    });
-                }));
-            });
+                        Observable.Range(0, 10, NewThreadScheduler.Default).ObserveOnDispatcher().Subscribe(x =>
+                        {
+                            okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                        },  () =>
+                        {
+                            Assert.True(okay);
+                            evt.Set();
+                        });
+                    }));
+                });
+            }
         }
 
         [Fact]
         [Asynchronous]
         public void ObserveOn_Error()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                var ex = new Exception();
-                bool okay = true;
-
-                dispatcher.BeginInvoke(new Action(() =>
+                RunAsync(evt =>
                 {
-                    Observable.Throw<int>(ex).ObserveOnDispatcher().Subscribe(x =>
-                    {
-                        okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                    },
-                    e =>
-                    {
-                        Assert.True(okay);
-                        Assert.Same(ex, e);
-                        dispatcher.InvokeShutdown();
-                        evt.Set();
-                    },
-                    () =>
+                    var ex = new Exception();
+                    bool okay = true;
+
+                    dispatcher.BeginInvoke(new Action(() =>
                     {
-                        Assert.True(false);
-                        dispatcher.InvokeShutdown();
-                        evt.Set();
-                    });
-                }));
-            });
+                        Observable.Throw<int>(ex).ObserveOnDispatcher().Subscribe(x =>
+                        {
+                            okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                        },
+                        e =>
+                        {
+                            Assert.True(okay);
+                            Assert.Same(ex, e);
+                            evt.Set();
+                        },
+                        () =>
+                        {
+                            Assert.True(false);
+                            evt.Set();
+                        });
+                    }));
+                });
+            }
         }
 #endif
         #endregion + TestBase +

+ 105 - 122
Rx.NET/Source/tests/Tests.System.Reactive/Tests/Linq/Observable/SubscribeOnTest.cs

@@ -57,166 +57,115 @@ namespace ReactiveTests.Tests
         [Fact]
         public void SubscribeOn_Control()
         {
-            var lbl = CreateLabel();
-
-            var evt2 = new ManualResetEvent(false);
-            var evt = new ManualResetEvent(false);
             bool okay = true;
-            var d = Observable.Create<int>(obs =>
-            {
-                lbl.Text = "Subscribe";
-                okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-                evt2.Set();
-
-                return () =>
-                {
-                    lbl.Text = "Unsubscribe";
-                    okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-                    evt.Set();
-                };
-            })
-            .SubscribeOn(lbl)
-            .Subscribe(_ => {});
-
-            evt2.WaitOne();
-            d.Dispose();
 
-            evt.WaitOne();
-            Application.Exit();
-            Assert.True(okay);
-        }
-
-        [Fact]
-        public void SubscribeOn_ControlScheduler()
-        {
-            var lbl = CreateLabel();
-
-            var evt2 = new ManualResetEvent(false);
-            var evt = new ManualResetEvent(false);
-            bool okay = true;
-            var d = Observable.Create<int>(obs =>
+            using (WinFormsTestUtils.RunTest(out var lbl))
             {
-                lbl.Text = "Subscribe";
-                okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-                evt2.Set();
-
-                return () =>
+                var evt2 = new ManualResetEvent(false);
+                var evt = new ManualResetEvent(false);
+                var d = Observable.Create<int>(obs =>
                 {
-                    lbl.Text = "Unsubscribe";
+                    lbl.Text = "Subscribe";
                     okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
-                    evt.Set();
-                };
-            })
-            .SubscribeOn(new ControlScheduler(lbl))
-            .Subscribe(_ => { });
+                    evt2.Set();
 
-            evt2.WaitOne();
-            d.Dispose();
-
-            evt.WaitOne();
-            Application.Exit();
-            Assert.True(okay);
-        }
+                    return () =>
+                    {
+                        lbl.Text = "Unsubscribe";
+                        okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
+                        evt.Set();
+                    };
+                })
+                .SubscribeOn(lbl)
+                .Subscribe(_ => {});
 
-        private Label CreateLabel()
-        {
-            var loaded = new ManualResetEvent(false);
-            var lbl = default(Label);
+                evt2.WaitOne();
+                d.Dispose();
 
-            var t = new Thread(() =>
-            {
-                lbl = new Label();
-                var frm = new Form { Controls = { lbl }, Width = 0, Height = 0, FormBorderStyle = FormBorderStyle.None, ShowInTaskbar = false };
-                frm.Load += (_, __) =>
-                {
-                    loaded.Set();
-                };
-                Application.Run(frm);
-            });
-            t.SetApartmentState(ApartmentState.STA);
-            t.Start();
+                evt.WaitOne();
+            }
 
-            loaded.WaitOne();
-            return lbl;
+            Assert.True(okay);
         }
-#endif
 
-#if HAS_DISPATCHER
         [Fact]
-        [Asynchronous]
-        public void SubscribeOn_Dispatcher()
+        public void SubscribeOn_ControlScheduler()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
+            bool okay = true;
 
-            RunAsync(evt =>
+            using (WinFormsTestUtils.RunTest(out var lbl))
             {
-                var s = new AsyncSubject<Unit>();
-                bool okay = true;
+                var evt2 = new ManualResetEvent(false);
+                var evt = new ManualResetEvent(false);
+                
                 var d = Observable.Create<int>(obs =>
                 {
-                    okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                    s.OnNext(Unit.Default);
-                    s.OnCompleted();
+                    lbl.Text = "Subscribe";
+                    okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
+                    evt2.Set();
 
                     return () =>
                     {
-                        okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                        Assert.True(okay);
-                        dispatcher.InvokeShutdown();
+                        lbl.Text = "Unsubscribe";
+                        okay &= (SynchronizationContext.Current is System.Windows.Forms.WindowsFormsSynchronizationContext);
                         evt.Set();
                     };
                 })
-                .SubscribeOn(dispatcher)
+                .SubscribeOn(new ControlScheduler(lbl))
                 .Subscribe(_ => { });
 
-                s.Subscribe(_ => d.Dispose());
-            });
+                evt2.WaitOne();
+                d.Dispose();
+
+                evt.WaitOne();
+            }
+
+            Assert.True(okay);
         }
+#endif
 
+#if HAS_DISPATCHER
         [Fact]
         [Asynchronous]
-        public void SubscribeOn_DispatcherScheduler()
+        public void SubscribeOn_Dispatcher()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                var s = new AsyncSubject<Unit>();
-                bool okay = true;
-                var d = Observable.Create<int>(obs =>
+                RunAsync(evt =>
                 {
-                    okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                    s.OnNext(Unit.Default);
-                    s.OnCompleted();
-
-                    return () =>
+                    var s = new AsyncSubject<Unit>();
+                    bool okay = true;
+                    var d = Observable.Create<int>(obs =>
                     {
                         okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
-                        Assert.True(okay);
-                        dispatcher.InvokeShutdown();
-                        evt.Set();
-                    };
-                })
-                .SubscribeOn(new DispatcherScheduler(dispatcher))
-                .Subscribe(_ => { });
+                        s.OnNext(Unit.Default);
+                        s.OnCompleted();
 
-                s.Subscribe(_ => d.Dispose());
-            });
+                        return () =>
+                        {
+                            okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                            Assert.True(okay);
+                            evt.Set();
+                        };
+                    })
+                    .SubscribeOn(dispatcher)
+                    .Subscribe(_ => { });
+
+                    s.Subscribe(_ => d.Dispose());
+                });
+            }
         }
 
         [Fact]
         [Asynchronous]
-        public void SubscribeOn_CurrentDispatcher()
+        public void SubscribeOn_DispatcherScheduler()
         {
-            var dispatcher = DispatcherHelpers.EnsureDispatcher();
-
-            RunAsync(evt =>
+            using (DispatcherHelpers.RunTest(out var dispatcher))
             {
-                var s = new AsyncSubject<Unit>();
-                bool okay = true;
-
-                dispatcher.BeginInvoke(new Action(() =>
+                RunAsync(evt =>
                 {
+                    var s = new AsyncSubject<Unit>();
+                    bool okay = true;
                     var d = Observable.Create<int>(obs =>
                     {
                         okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
@@ -227,16 +176,50 @@ namespace ReactiveTests.Tests
                         {
                             okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
                             Assert.True(okay);
-                            dispatcher.InvokeShutdown();
                             evt.Set();
                         };
                     })
-                    .SubscribeOnDispatcher()
+                    .SubscribeOn(new DispatcherScheduler(dispatcher))
                     .Subscribe(_ => { });
 
                     s.Subscribe(_ => d.Dispose());
-                }));
-            });
+                });
+            }
+        }
+
+        [Fact]
+        [Asynchronous]
+        public void SubscribeOn_CurrentDispatcher()
+        {
+            using (DispatcherHelpers.RunTest(out var dispatcher))
+            {
+                RunAsync(evt =>
+                {
+                    var s = new AsyncSubject<Unit>();
+                    bool okay = true;
+
+                    dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        var d = Observable.Create<int>(obs =>
+                        {
+                            okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                            s.OnNext(Unit.Default);
+                            s.OnCompleted();
+
+                            return () =>
+                            {
+                                okay &= (SynchronizationContext.Current is System.Windows.Threading.DispatcherSynchronizationContext);
+                                Assert.True(okay);
+                                evt.Set();
+                            };
+                        })
+                        .SubscribeOnDispatcher()
+                        .Subscribe(_ => { });
+
+                        s.Subscribe(_ => d.Dispose());
+                    }));
+                });
+            }
         }
 #endif
 

+ 69 - 0
Rx.NET/Source/tests/Tests.System.Reactive/WinFormsTestUtils.cs

@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT License.
+// See the LICENSE file in the project root for more information. 
+
+#if HAS_WINFORMS
+
+using System;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace ReactiveTests.Tests
+{
+    internal static class WinFormsTestUtils
+    {
+        private static readonly Semaphore s_oneWinForms = new Semaphore(1, 1);
+
+        public static IDisposable RunTest(out Label label)
+        {
+            s_oneWinForms.WaitOne();
+
+            var loaded = new ManualResetEvent(false);
+            var lbl = default(Label);
+
+            var t = new Thread(() =>
+            {
+                lbl = new Label();
+                var frm = new Form { Controls = { lbl }, Width = 0, Height = 0, FormBorderStyle = FormBorderStyle.None, ShowInTaskbar = false };
+                frm.Load += (_, __) =>
+                {
+                    loaded.Set();
+                };
+                Application.Run(frm);
+            });
+            t.SetApartmentState(ApartmentState.STA);
+            t.Start();
+
+            loaded.WaitOne();
+
+            label = lbl;
+            return new RunWinFormsTest(label, t);
+        }
+
+        private sealed class RunWinFormsTest : IDisposable
+        {
+            private readonly Label _lbl;
+            private readonly Thread _t;
+
+            public RunWinFormsTest(Label lbl, Thread t)
+            {
+                _lbl = lbl;
+                _t = t;
+            }
+
+            public void Dispose()
+            {
+                _lbl.Invoke(new Action(() =>
+                {
+                    Application.Exit();
+                }));
+
+                _t.Join();
+
+                s_oneWinForms.Release();
+            }
+        }
+    }
+}
+
+#endif