From 9be83e26a0d9d2c996cedb35bfa6ee179a78e3bb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 1 Feb 2020 13:46:51 +0100 Subject: [PATCH 1/8] Skipped dispatching when already on the UI thread --- .../Helpers/DispatcherHelper.cs | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index 1fb644509fa..04dca4a5706 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -147,14 +147,16 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< var taskCompletionSource = new TaskCompletionSource(); - var ignored = dispatcher.RunAsync(priority, async () => + _ = dispatcher.RunAsync(priority, async () => { try { var awaitableResult = function(); + if (awaitableResult != null) { var result = await awaitableResult.ConfigureAwait(false); + taskCompletionSource.SetResult(result); } else @@ -187,14 +189,16 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func var taskCompletionSource = new TaskCompletionSource(); - var ignored = dispatcher.RunAsync(priority, async () => + _ = dispatcher.RunAsync(priority, async () => { try { var awaitableResult = function(); + if (awaitableResult != null) { await awaitableResult.ConfigureAwait(false); + taskCompletionSource.SetResult(null); } else @@ -226,9 +230,16 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< throw new ArgumentNullException(nameof(function)); } + if (dispatcher.HasThreadAccess) + { + var result = function(); + + return Task.FromResult(result); + } + var taskCompletionSource = new TaskCompletionSource(); - var ignored = dispatcher.RunAsync(priority, () => + _ = dispatcher.RunAsync(priority, () => { try { @@ -252,12 +263,34 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< /// Awaitable Task public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - return dispatcher.AwaitableRunAsync( - () => + if (function == null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (dispatcher.HasThreadAccess) + { + function(); + + return Task.CompletedTask; + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.RunAsync(priority, () => + { + try { function(); - return (object)null; - }, priority); + taskCompletionSource.SetResult(null); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; } } } From ae4e43da74b4a7771dbd7807d65b400be47089db Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 1 Feb 2020 13:47:35 +0100 Subject: [PATCH 2/8] APIs reordered --- .../Helpers/DispatcherHelper.cs | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index 04dca4a5706..cd5a3c4910a 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -15,59 +15,47 @@ namespace Microsoft.Toolkit.Uwp.Helpers public static class DispatcherHelper { /// - /// Executes the given function asynchronously on UI thread of the main view. + /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. /// - /// Returned data type of the function /// Asynchronous function to be executed asynchronously on UI thread /// Dispatcher execution priority, default is normal - /// Awaitable Task with type - public static Task ExecuteOnUIThreadAsync(Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// Awaitable Task/> + public static Task ExecuteOnUIThreadAsync(Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); + return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } /// /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. /// /// Returned data type of the function - /// View for the to be executed on - /// Asynchronous function to be executed asynchronously on UI thread + /// Synchronous function to be executed on UI thread /// Dispatcher execution priority, default is normal - /// Awaitable Task with type - public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// Awaitable Task + public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) - { - throw new ArgumentNullException(nameof(viewToExecuteOn)); - } - - return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); + return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function asynchronously on UI thread of the main view. /// - /// View for the to be executed on /// Asynchronous function to be executed asynchronously on UI thread /// Dispatcher execution priority, default is normal /// Awaitable Task - public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) - { - throw new ArgumentNullException(nameof(viewToExecuteOn)); - } - - return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); + return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } /// /// Executes the given function asynchronously on UI thread of the main view. /// + /// Returned data type of the function /// Asynchronous function to be executed asynchronously on UI thread /// Dispatcher execution priority, default is normal - /// Awaitable Task - public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// Awaitable Task with type + public static Task ExecuteOnUIThreadAsync(Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } @@ -89,17 +77,6 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecute return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); } - /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. - /// - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task/> - public static Task ExecuteOnUIThreadAsync(Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) - { - return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); - } - /// /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. /// @@ -115,96 +92,73 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToE throw new ArgumentNullException(nameof(viewToExecuteOn)); } - return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); + return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); } /// /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. /// - /// Returned data type of the function - /// Synchronous function to be executed on UI thread + /// View for the to be executed on + /// Asynchronous function to be executed asynchronously on UI thread /// Dispatcher execution priority, default is normal - /// Awaitable Task - public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// Awaitable Task + public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); + if (viewToExecuteOn == null) + { + throw new ArgumentNullException(nameof(viewToExecuteOn)); + } + + return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); } /// - /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. /// /// Returned data type of the function - /// Dispatcher of a thread to run - /// Asynchrounous function to be executed asynchrounously on the given dispatcher + /// View for the to be executed on + /// Asynchronous function to be executed asynchronously on UI thread /// Dispatcher execution priority, default is normal /// Awaitable Task with type - public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (function == null) + if (viewToExecuteOn == null) { - throw new ArgumentNullException(nameof(function)); + throw new ArgumentNullException(nameof(viewToExecuteOn)); } - var taskCompletionSource = new TaskCompletionSource(); - - _ = dispatcher.RunAsync(priority, async () => - { - try - { - var awaitableResult = function(); - - if (awaitableResult != null) - { - var result = await awaitableResult.ConfigureAwait(false); - - taskCompletionSource.SetResult(result); - } - else - { - taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); - } - } - catch (Exception e) - { - taskCompletionSource.SetException(e); - } - }); - - return taskCompletionSource.Task; + return viewToExecuteOn.Dispatcher.AwaitableRunAsync(function, priority); } /// /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher /// /// Dispatcher of a thread to run - /// Asynchrounous function to be executed asynchrounously on the given dispatcher + /// Function to be executed asynchrounously on the given dispatcher /// Dispatcher execution priority, default is normal /// Awaitable Task - public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function == null) { throw new ArgumentNullException(nameof(function)); } + if (dispatcher.HasThreadAccess) + { + function(); + + return Task.CompletedTask; + } + var taskCompletionSource = new TaskCompletionSource(); - _ = dispatcher.RunAsync(priority, async () => + _ = dispatcher.RunAsync(priority, () => { try { - var awaitableResult = function(); - - if (awaitableResult != null) - { - await awaitableResult.ConfigureAwait(false); - - taskCompletionSource.SetResult(null); - } - else - { - taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); - } + function(); + taskCompletionSource.SetResult(null); } catch (Exception e) { @@ -258,31 +212,77 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher /// /// Dispatcher of a thread to run - /// Function to be executed asynchrounously on the given dispatcher + /// Asynchrounous function to be executed asynchrounously on the given dispatcher /// Dispatcher execution priority, default is normal /// Awaitable Task - public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function == null) { throw new ArgumentNullException(nameof(function)); } - if (dispatcher.HasThreadAccess) + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.RunAsync(priority, async () => { - function(); + try + { + var awaitableResult = function(); - return Task.CompletedTask; + if (awaitableResult != null) + { + await awaitableResult.ConfigureAwait(false); + + taskCompletionSource.SetResult(null); + } + else + { + taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + /// + /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// + /// Returned data type of the function + /// Dispatcher of a thread to run + /// Asynchrounous function to be executed asynchrounously on the given dispatcher + /// Dispatcher execution priority, default is normal + /// Awaitable Task with type + public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + { + if (function == null) + { + throw new ArgumentNullException(nameof(function)); } - var taskCompletionSource = new TaskCompletionSource(); + var taskCompletionSource = new TaskCompletionSource(); - _ = dispatcher.RunAsync(priority, () => + _ = dispatcher.RunAsync(priority, async () => { try { - function(); - taskCompletionSource.SetResult(null); + var awaitableResult = function(); + + if (awaitableResult != null) + { + var result = await awaitableResult.ConfigureAwait(false); + + taskCompletionSource.SetResult(result); + } + else + { + taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); + } } catch (Exception e) { From 84b2b370921d992ff559d6008cb77d5f644c6b16 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 1 Feb 2020 14:07:20 +0100 Subject: [PATCH 3/8] Added comments --- Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index cd5a3c4910a..83b59147b4f 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -146,6 +146,11 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action func if (dispatcher.HasThreadAccess) { + /* There is no need to manually handle the exceptions in this case. + * Since we're not scheduling a callback, exceptions thrown by the + * function in this path will correctly end up in the stack trace + * when the returned task is awaited anyway. + * This also saves a heap allocation, as the returned task is reused. */ function(); return Task.CompletedTask; @@ -158,6 +163,7 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action func try { function(); + taskCompletionSource.SetResult(null); } catch (Exception e) @@ -186,6 +192,10 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< if (dispatcher.HasThreadAccess) { + /* Just like with a simple action, we don't need to manually + * handle failures here. As we're not in a dispatched callback, + * exceptions thrown at this point will just show up correctly + * in the stack trace when the returned task is awaited. */ var result = function(); return Task.FromResult(result); From 9dd37e36d028b3692878980a297c23a9c546fd9d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 2 Feb 2020 13:05:48 +0100 Subject: [PATCH 4/8] Reintroduced manual failure handing --- .../Helpers/DispatcherHelper.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index 83b59147b4f..ca7d6dc365c 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -146,14 +146,19 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action func if (dispatcher.HasThreadAccess) { - /* There is no need to manually handle the exceptions in this case. - * Since we're not scheduling a callback, exceptions thrown by the - * function in this path will correctly end up in the stack trace - * when the returned task is awaited anyway. - * This also saves a heap allocation, as the returned task is reused. */ - function(); - - return Task.CompletedTask; + /* Run the function directly when we have thread access. + * Also reuse Task.CompletedTask in case of success, + * to skip an unnecessary heap allocation for every invocation. */ + try + { + function(); + + return Task.CompletedTask; + } + catch (Exception e) + { + return Task.FromException(e); + } } var taskCompletionSource = new TaskCompletionSource(); @@ -192,13 +197,17 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< if (dispatcher.HasThreadAccess) { - /* Just like with a simple action, we don't need to manually - * handle failures here. As we're not in a dispatched callback, - * exceptions thrown at this point will just show up correctly - * in the stack trace when the returned task is awaited. */ - var result = function(); + // Skip the dispatch, if posssible + try + { + var result = function(); - return Task.FromResult(result); + return Task.FromResult(result); + } + catch (Exception e) + { + return Task.FromException(e); + } } var taskCompletionSource = new TaskCompletionSource(); From 7d41f78e788cb1d7bb6056656f5baede0cb6da30 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 2 Feb 2020 13:13:52 +0100 Subject: [PATCH 5/8] Added UI thread dispatch skip to Task/Task overloads --- .../Helpers/DispatcherHelper.cs | 75 +++++++++++++------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index ca7d6dc365c..5500fcbbe1e 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -69,7 +69,7 @@ public static Task ExecuteOnUIThreadAsync(Func> function, CoreDisp /// Awaitable Task/> public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) + if (viewToExecuteOn is null) { throw new ArgumentNullException(nameof(viewToExecuteOn)); } @@ -87,7 +87,7 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecute /// Awaitable Task with type public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) + if (viewToExecuteOn is null) { throw new ArgumentNullException(nameof(viewToExecuteOn)); } @@ -104,7 +104,7 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToE /// Awaitable Task public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) + if (viewToExecuteOn is null) { throw new ArgumentNullException(nameof(viewToExecuteOn)); } @@ -122,7 +122,7 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecute /// Awaitable Task with type public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (viewToExecuteOn == null) + if (viewToExecuteOn is null) { throw new ArgumentNullException(nameof(viewToExecuteOn)); } @@ -139,16 +139,16 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToE /// Awaitable Task public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (function == null) + if (function is null) { throw new ArgumentNullException(nameof(function)); } + /* Run the function directly when we have thread access. + * Also reuse Task.CompletedTask in case of success, + * to skip an unnecessary heap allocation for every invocation. */ if (dispatcher.HasThreadAccess) { - /* Run the function directly when we have thread access. - * Also reuse Task.CompletedTask in case of success, - * to skip an unnecessary heap allocation for every invocation. */ try { function(); @@ -190,19 +190,17 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action func /// Awaitable Task public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (function == null) + if (function is null) { throw new ArgumentNullException(nameof(function)); } + // Skip the dispatch, if posssible if (dispatcher.HasThreadAccess) { - // Skip the dispatch, if posssible try { - var result = function(); - - return Task.FromResult(result); + return Task.FromResult(function()); } catch (Exception e) { @@ -236,20 +234,39 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< /// Awaitable Task public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (function == null) + if (function is null) { throw new ArgumentNullException(nameof(function)); } + /* If we have thread access, we can retrieve the task directly. + * We don't use ConfigureAwait(false) in this case, in order + * to let the caller continue its execution on the same thread + * after awaiting the task returned by this function. */ + if (dispatcher.HasThreadAccess) + { + try + { + if (function() is Task awaitableResult) + { + return awaitableResult; + } + + return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + var taskCompletionSource = new TaskCompletionSource(); _ = dispatcher.RunAsync(priority, async () => { try { - var awaitableResult = function(); - - if (awaitableResult != null) + if (function() is Task awaitableResult) { await awaitableResult.ConfigureAwait(false); @@ -279,20 +296,36 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func /// Awaitable Task with type public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { - if (function == null) + if (function is null) { throw new ArgumentNullException(nameof(function)); } + // Skip the dispatch, if posssible + if (dispatcher.HasThreadAccess) + { + try + { + if (function() is Task awaitableResult) + { + return awaitableResult; + } + + return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + var taskCompletionSource = new TaskCompletionSource(); _ = dispatcher.RunAsync(priority, async () => { try { - var awaitableResult = function(); - - if (awaitableResult != null) + if (function() is Task awaitableResult) { var result = await awaitableResult.ConfigureAwait(false); From 1cebda0c2909f4f652d80675457fb1a7fbbdfce3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 3 Feb 2020 14:51:51 +0100 Subject: [PATCH 6/8] Improved XML comments --- .../Helpers/DispatcherHelper.cs | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index 5500fcbbe1e..ee309ae1cc2 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -15,23 +15,25 @@ namespace Microsoft.Toolkit.Uwp.Helpers public static class DispatcherHelper { /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. The default view is the main view. /// - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task/> + /// Synchronous function to be executed on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. The default view is the main view. /// - /// Returned data type of the function - /// Synchronous function to be executed on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// Returned data type of the function. + /// Synchronous function to be executed on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); @@ -40,9 +42,9 @@ public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcher /// /// Executes the given function asynchronously on UI thread of the main view. /// - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// Asynchronous function to be executed asynchronously on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); @@ -51,22 +53,23 @@ public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPri /// /// Executes the given function asynchronously on UI thread of the main view. /// - /// Returned data type of the function - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task with type + /// Returned data type of the function. + /// Asynchronous function to be executed asynchronously on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. public static Task ExecuteOnUIThreadAsync(Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. /// - /// View for the to be executed on - /// Asynchronous function to be executed asynchronously on UI thread + /// View for the to be executed on. + /// Synchronous function to be executed on UI thread. /// Dispatcher execution priority, default is normal - /// Awaitable Task/> + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (viewToExecuteOn is null) @@ -78,13 +81,14 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecute } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. /// - /// Returned data type of the function - /// View for the to be executed on - /// Synchronous function with return type to be executed on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task with type + /// Returned data type of the function. + /// View for the to be executed on. + /// Synchronous function with return type to be executed on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (viewToExecuteOn is null) @@ -96,12 +100,13 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToE } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. /// - /// View for the to be executed on - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// View for the to be executed on. + /// Asynchronous function to be executed asynchronously on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (viewToExecuteOn is null) @@ -113,13 +118,14 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecute } /// - /// Executes the given function asynchronously on given view's UI thread. Default view is the main view. + /// Executes the given function on a given view's UI thread. /// - /// Returned data type of the function - /// View for the to be executed on - /// Asynchronous function to be executed asynchronously on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task with type + /// Returned data type of the function. + /// View for the to be executed on. + /// Asynchronous function to be executed asynchronously on UI thread. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (viewToExecuteOn is null) @@ -131,12 +137,13 @@ public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToE } /// - /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. /// - /// Dispatcher of a thread to run - /// Function to be executed asynchrounously on the given dispatcher - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// Dispatcher of a thread to run . + /// Function to be executed on the given dispatcher. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function is null) @@ -181,13 +188,14 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action func } /// - /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. /// - /// Returned data type of the function - /// Dispatcher of a thread to run - /// Function to be executed asynchrounously on the given dispatcher - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// Returned data type of the function. + /// Dispatcher of a thread to run . + /// Function to be executed on the given dispatcher. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function is null) @@ -226,12 +234,13 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< } /// - /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. /// - /// Dispatcher of a thread to run - /// Asynchrounous function to be executed asynchrounously on the given dispatcher - /// Dispatcher execution priority, default is normal - /// Awaitable Task + /// Dispatcher of a thread to run . + /// Asynchrounous function to be executed on the given dispatcher. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function is null) @@ -287,13 +296,14 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func } /// - /// Extension method for CoreDispatcher. Offering an actual awaitable Task with optional result that will be executed on the given dispatcher + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. /// - /// Returned data type of the function - /// Dispatcher of a thread to run - /// Asynchrounous function to be executed asynchrounously on the given dispatcher - /// Dispatcher execution priority, default is normal - /// Awaitable Task with type + /// Returned data type of the function. + /// Dispatcher of a thread to run . + /// Asynchrounous function to be executed asynchrounously on the given dispatcher. + /// Dispatcher execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) { if (function is null) From 899d50081f1507a2ce7154c6355b22240367408b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 24 Mar 2020 23:15:11 +0100 Subject: [PATCH 7/8] Added tests for the DispatcherHelper type --- UnitTests/Helpers/Test_DispatcherHelper.cs | 221 +++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 2 files changed, 222 insertions(+) create mode 100644 UnitTests/Helpers/Test_DispatcherHelper.cs diff --git a/UnitTests/Helpers/Test_DispatcherHelper.cs b/UnitTests/Helpers/Test_DispatcherHelper.cs new file mode 100644 index 00000000000..3f6803e65fb --- /dev/null +++ b/UnitTests/Helpers/Test_DispatcherHelper.cs @@ -0,0 +1,221 @@ +// 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. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System.Linq; +using System.Threading.Tasks; +using Windows.UI.Xaml.Controls; +using Microsoft.Toolkit.Uwp.Helpers; + +namespace UnitTests.Helpers +{ + [TestClass] + public class Test_DispatcherHelper + { + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherHelper_Action_Null() + { + DispatcherHelper.ExecuteOnUIThreadAsync(default(Action)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_Action_Ok_UIThread() + { + DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_Action_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_Action_Ok_NonUIThread)); + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherHelper_Action_Ok_NonUIThread() + { + await DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_Action_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_Action_Ok_NonUIThread)); + }); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_Action_Exception() + { + var task = DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherHelper_Action_Exception)); + }); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherHelper_FuncOfT_Null() + { + DispatcherHelper.ExecuteOnUIThreadAsync(default(Func)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfT_Ok_UIThread() + { + var textBlock = DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + return new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread) }; + }).Result; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread)); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherHelper_FuncOfT_Ok_NonUIThread() + { + var textBlock = await DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + return new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread) }; + }); + + await DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread)); + }); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfT_Exception() + { + var task = DispatcherHelper.ExecuteOnUIThreadAsync(new Func(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherHelper_FuncOfT_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + [SuppressMessage("Style", "IDE0034", Justification = "Explicit overload for clarity")] + public void Test_DispatcherHelper_FuncOfTask_Null() + { + DispatcherHelper.ExecuteOnUIThreadAsync(default(Func)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfTask_Ok_UIThread() + { + DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread)); + + return Task.CompletedTask; + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread() + { + await DispatcherHelper.ExecuteOnUIThreadAsync(async () => + { + await Task.Yield(); + + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread)); + }); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfTask_Exception() + { + var task = DispatcherHelper.ExecuteOnUIThreadAsync(new Func(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherHelper_FuncOfTask_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherHelper_FuncOfTaskOfT_Null() + { + DispatcherHelper.ExecuteOnUIThreadAsync(default(Func>)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfTaskOfT_Ok_UIThread() + { + DispatcherHelper.ExecuteOnUIThreadAsync(() => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread)); + + return Task.FromResult(1); + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread() + { + await DispatcherHelper.ExecuteOnUIThreadAsync(async () => + { + await Task.Yield(); + + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread)); + + return textBlock; + }); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherHelper_FuncOfTaskOfT_Exception() + { + var task = DispatcherHelper.ExecuteOnUIThreadAsync(new Func>(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherHelper_FuncOfTaskOfT_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index b4208dc4fef..bde259aea19 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -141,6 +141,7 @@ + From d319fb937946904619d1d0842dd69d03d74c6645 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 30 Mar 2020 15:32:00 +0200 Subject: [PATCH 8/8] Fixed some XML comments --- Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index ee309ae1cc2..e1389f5122e 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -15,7 +15,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers public static class DispatcherHelper { /// - /// Executes the given function on a given view's UI thread. The default view is the main view. + /// Executes the given function on the main view's UI thread. /// /// Synchronous function to be executed on UI thread. /// Dispatcher execution priority, default is normal. @@ -27,7 +27,7 @@ public static Task ExecuteOnUIThreadAsync(Action function, CoreDispatcherPriorit } /// - /// Executes the given function on a given view's UI thread. The default view is the main view. + /// Executes the given function on the main view's UI thread and returns its result. /// /// Returned data type of the function. /// Synchronous function to be executed on UI thread. @@ -40,7 +40,8 @@ public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcher } /// - /// Executes the given function asynchronously on UI thread of the main view. + /// Executes the given -returning function on the main view's UI thread and returns either that + /// or a proxy that completes when the one produced by the given function completes. /// /// Asynchronous function to be executed asynchronously on UI thread. /// Dispatcher execution priority, default is normal. @@ -51,7 +52,8 @@ public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPri } /// - /// Executes the given function asynchronously on UI thread of the main view. + /// Executes the given -returning function on the main view's UI thread and returns either that + /// or a proxy that completes when the one produced by the given function completes. /// /// Returned data type of the function. /// Asynchronous function to be executed asynchronously on UI thread.