diff --git a/Stylet/Execute.cs b/Stylet/Execute.cs index 19be3f6..1e61833 100644 --- a/Stylet/Execute.cs +++ b/Stylet/Execute.cs @@ -84,6 +84,7 @@ namespace Stylet /// /// Dispatches the given action to be run on the UI thread asynchronously, even if the current thread is the UI thread /// + /// Action to run on the UI thread public static void PostToUIThread(Action action) { EnsureDispatcher(); @@ -93,9 +94,29 @@ namespace Stylet action(); } + /// + /// Dispatches the given action to be run on the UI thread asynchronously, and returns a task which completes when the action completes, even if the current thread is the UI thread + /// + /// Action to run on the UI thread + /// Task which completes when the action has been run + public static Task PostToUIThreadAsync(Action action) + { + EnsureDispatcher(); + if (!TestExecuteSynchronously) + { + return PostOnUIThreadInternalAsync(action); + } + else + { + action(); + return Task.FromResult(false); + } + } + /// /// Dispatches the given action to be run on the UI thread asynchronously, or runs it synchronously if the current thread is the UI thread /// + /// Action to run on the UI thread public static void OnUIThread(Action action) { EnsureDispatcher(); @@ -108,6 +129,7 @@ namespace Stylet /// /// Dispatches the given action to be run on the UI thread and blocks until it completes, or runs it synchronously if the current thread is the UI thread /// + /// Action to run on the UI thread public static void OnUIThreadSync(Action action) { EnsureDispatcher(); @@ -138,25 +160,14 @@ namespace Stylet /// /// Dispatches the given action to be run on the UI thread and returns a task that completes when the action completes, or runs it synchronously if the current thread is the UI thread /// + /// Action to run on the UI thread + /// Task which completes when the action has been run public static Task OnUIThreadAsync(Action action) { EnsureDispatcher(); if (!TestExecuteSynchronously && !Dispatcher.IsCurrent) { - var tcs = new TaskCompletionSource(); - Dispatcher.Post(() => - { - try - { - action(); - tcs.SetResult(null); - } - catch (Exception e) - { - tcs.SetException(e); - } - }); - return tcs.Task; + return PostOnUIThreadInternalAsync(action); } else { @@ -165,6 +176,24 @@ namespace Stylet } } + private static Task PostOnUIThreadInternalAsync(Action action) + { + var tcs = new TaskCompletionSource(); + Dispatcher.Post(() => + { + try + { + action(); + tcs.SetResult(null); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + return tcs.Task; + } + /// /// Determing if we're currently running in design mode /// diff --git a/StyletUnitTests/ExecuteTests.cs b/StyletUnitTests/ExecuteTests.cs index 66e2732..40d4830 100644 --- a/StyletUnitTests/ExecuteTests.cs +++ b/StyletUnitTests/ExecuteTests.cs @@ -21,7 +21,7 @@ namespace StyletUnitTests } [Test] - public void OnUIThreadExecutesUsingDispatcher() + public void OnUIThreadSyncExecutesUsingDispatcher() { var sync = new Mock(); Execute.Dispatcher = sync.Object; @@ -38,7 +38,22 @@ namespace StyletUnitTests } [Test] - public void BeginOnUIThreadExecutesUsingDispatcher() + public void OnUIThreadSyncExecutesSynchronouslyIfDispatcherIsCurrent() + { + var sync = new Mock(); + Execute.Dispatcher = sync.Object; + + sync.SetupGet(x => x.IsCurrent).Returns(true); + + bool actionCalled = false; + Execute.OnUIThreadSync(() => actionCalled = true); + + Assert.IsTrue(actionCalled); + sync.Verify(x => x.Send(It.IsAny()), Times.Never); + } + + [Test] + public void PostToUIThreadExecutesUsingDispatcher() { var sync = new Mock(); Execute.Dispatcher = sync.Object; @@ -55,7 +70,26 @@ namespace StyletUnitTests } [Test] - public void BeginOnUIThreadOrSynchronousExecutesUsingDispatcherIfNotCurrent() + public void PostToUIThreadAsyncExecutesUsingDispatcher() + { + var sync = new Mock(); + Execute.Dispatcher = sync.Object; + + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); + + bool actionCalled = false; + var task = Execute.PostToUIThreadAsync(() => actionCalled = true); + + Assert.IsFalse(task.IsCompleted); + Assert.IsFalse(actionCalled); + passedAction(); + Assert.IsTrue(actionCalled); + Assert.IsTrue(task.IsCompleted); + } + + [Test] + public void OnUIThreadExecutesUsingDispatcherIfNotCurrent() { var sync = new Mock(); Execute.Dispatcher = sync.Object; @@ -91,7 +125,7 @@ namespace StyletUnitTests } [Test] - public void OnUIThreadPropagatesException() + public void OnUIThreadSyncPropagatesException() { var sync = new Mock(); Execute.Dispatcher = sync.Object; @@ -125,21 +159,45 @@ namespace StyletUnitTests } [Test] - public void ThrowsIfBeginOnUIThreadCalledWithNoDispatcher() + public void PostToUIThreadAsyncPrepagatesException() + { + var sync = new Mock(); + Execute.Dispatcher = sync.Object; + + Action passedAction = null; + sync.Setup(x => x.Post(It.IsAny())).Callback((Action a) => passedAction = a); + + var ex = new Exception("test"); + var task = Execute.PostToUIThreadAsync(() => { throw ex; }); + + passedAction(); + Assert.IsTrue(task.IsFaulted); + Assert.AreEqual(ex, task.Exception.InnerExceptions[0]); + } + + [Test] + public void ThrowsIfPostToUIThreadCalledWithNoDispatcher() { Execute.Dispatcher = null; Assert.Throws(() => Execute.PostToUIThread(() => { })); } [Test] - public void ThrowsIfBeginOnUIThreadOrSynchronousCalledWithNoDispatcher() + public void ThrowsIfPostToUIThreadAsyncCalledWithNoDispatcher() + { + Execute.Dispatcher = null; + Assert.Throws(() => Execute.PostToUIThreadAsync(() => { })); + } + + [Test] + public void ThrowsIfOnUIThreadCalledWithNoDispatcher() { Execute.Dispatcher = null; Assert.Throws(() => Execute.OnUIThread(() => { })); } [Test] - public void ThrowsIfOnUIThreadCalledWithNoDispatcher() + public void ThrowsIfOnUIThreadSyncCalledWithNoDispatcher() { Execute.Dispatcher = null; Assert.Throws(() => Execute.OnUIThreadSync(() => { })); @@ -153,7 +211,7 @@ namespace StyletUnitTests } [Test] - public void BeginOnUIThreadExecutesSynchronouslyIfTestExecuteSynchronouslySet() + public void PostToUIThreadExecutesSynchronouslyIfTestExecuteSynchronouslySet() { Execute.TestExecuteSynchronously = true; @@ -164,7 +222,19 @@ namespace StyletUnitTests } [Test] - public void OnUIThreadExecutesSynchronouslyIfTestExecuteSynchronouslySet() + public void PostToUIThreadAsyncExecutesSynchronouslyIfTestExecuteSynchronouslySet() + { + Execute.TestExecuteSynchronously = true; + + Execute.Dispatcher = null; + bool called = false; + var task = Execute.PostToUIThreadAsync(() => called = true); + Assert.True(called); + Assert.True(task.IsCompleted); + } + + [Test] + public void OnUIThreadSyncExecutesSynchronouslyIfTestExecuteSynchronouslySet() { Execute.TestExecuteSynchronously = true;