diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index 1fb644509fa..e1389f5122e 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -15,73 +15,66 @@ 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 on the main view's UI thread. /// - /// 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) + /// 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); + 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 the main view's UI thread and returns its result. /// - /// 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 - public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// 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) { - 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 -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. /// - /// 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) + /// 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) { - 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. + /// 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 - /// Awaitable Task - public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// 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 == null) + if (viewToExecuteOn is null) { throw new ArgumentNullException(nameof(viewToExecuteOn)); } @@ -90,77 +83,148 @@ 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. /// - /// 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) + /// 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) { - return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); + if (viewToExecuteOn is null) + { + throw new ArgumentNullException(nameof(viewToExecuteOn)); + } + + return viewToExecuteOn.Dispatcher.AwaitableRunAsync(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. /// - /// 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 - public static Task ExecuteOnUIThreadAsync(this CoreApplicationView viewToExecuteOn, Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// 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 == null) + if (viewToExecuteOn is null) { 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. + /// Executes the given function on a given view's UI thread. /// - /// Returned data type of the function - /// Synchronous function to be executed on UI thread - /// Dispatcher execution priority, default is normal - /// Awaitable Task - public static Task ExecuteOnUIThreadAsync(Func function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// 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) { - return ExecuteOnUIThreadAsync(CoreApplication.MainView, function, priority); + if (viewToExecuteOn is 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 + /// 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 - public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func> function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) + /// 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 == 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) + { + try + { + function(); + + return Task.CompletedTask; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.RunAsync(priority, () => + { + try + { + function(); + + taskCompletionSource.SetResult(null); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + /// + /// 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 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) + { + throw new ArgumentNullException(nameof(function)); + } + + // Skip the dispatch, if posssible + if (dispatcher.HasThreadAccess) + { + try + { + return Task.FromResult(function()); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + var taskCompletionSource = new TaskCompletionSource(); - var ignored = dispatcher.RunAsync(priority, async () => + _ = dispatcher.RunAsync(priority, () => { 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.")); - } + taskCompletionSource.SetResult(function()); } catch (Exception e) { @@ -172,29 +236,51 @@ 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 == 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(); - var ignored = dispatcher.RunAsync(priority, async () => + _ = dispatcher.RunAsync(priority, async () => { try { - var awaitableResult = function(); - if (awaitableResult != null) + if (function() is Task awaitableResult) { await awaitableResult.ConfigureAwait(false); + taskCompletionSource.SetResult(null); } else @@ -212,27 +298,55 @@ 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 - /// 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) + /// 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 == 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(); - var ignored = dispatcher.RunAsync(priority, () => + _ = dispatcher.RunAsync(priority, async () => { try { - taskCompletionSource.SetResult(function()); + if (function() is Task awaitableResult) + { + 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) { @@ -242,22 +356,5 @@ public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Func< return taskCompletionSource.Task; } - - /// - /// 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 - /// Dispatcher execution priority, default is normal - /// Awaitable Task - public static Task AwaitableRunAsync(this CoreDispatcher dispatcher, Action function, CoreDispatcherPriority priority = CoreDispatcherPriority.Normal) - { - return dispatcher.AwaitableRunAsync( - () => - { - function(); - return (object)null; - }, priority); - } } } 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 @@ +