From d2651f1affbd434464280fd3ee352ccb3b188c6a Mon Sep 17 00:00:00 2001 From: Laurent Bugnion Date: Mon, 23 Sep 2013 13:52:51 +0200 Subject: [PATCH] Better error handling for DispatcherHelper when not initialized Added a way to reset the dispatcher in DispatcherHelper --- .../Threading/DispatcherHelper.cs | 46 ++++- .../Threading/DispatcherHelperTest.cs | 167 ++++++++++++------ 2 files changed, 159 insertions(+), 54 deletions(-) diff --git a/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/Threading/DispatcherHelper.cs b/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/Threading/DispatcherHelper.cs index 5a2d189..d31d47c 100644 --- a/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/Threading/DispatcherHelper.cs +++ b/GalaSoft.MvvmLight/GalaSoft.MvvmLight (NET35)/Threading/DispatcherHelper.cs @@ -15,6 +15,7 @@ // **************************************************************************** using System; +using System.Text; #if NETFX_CORE using Windows.UI.Core; @@ -36,8 +37,8 @@ namespace GalaSoft.MvvmLight.Threading /// Helper class for dispatcher operations on the UI thread. /// //// [ClassInfo(typeof(DispatcherHelper), - //// VersionString = "4.0.4", - //// DateString = "201206191330", + //// VersionString = "4.1.6", + //// DateString = "201305190047", //// Description = "Helper class for dispatcher operations on the UI thread.", //// UrlContacts = "http://www.galasoft.ch/contact_en.html", //// Email = "laurent@galasoft.ch")] @@ -70,6 +71,13 @@ namespace GalaSoft.MvvmLight.Threading /// thread. public static void CheckBeginInvokeOnUI(Action action) { + if (action == null) + { + return; + } + + CheckDispatcher(); + #if NETFX_CORE if (UIDispatcher.HasThreadAccess) #else @@ -88,6 +96,30 @@ namespace GalaSoft.MvvmLight.Threading } } + private static void CheckDispatcher() + { + if (UIDispatcher == null) + { + var error = new StringBuilder("The DispatcherHelper is not initialized."); + error.AppendLine(); + +#if SILVERLIGHT +#if WINDOWS_PHONE + error.Append("Call DispatcherHelper.Initialize() at the end of App.InitializePhoneApplication."); +#else + error.Append("Call DispatcherHelper.Initialize() in Application_Startup (App.xaml.cs)."); +#endif +#elif NETFX_CORE + error.Append("Call DispatcherHelper.Initialize() at the end of App.OnLaunched."); +#else + error.Append("Call DispatcherHelper.Initialize() in the static App constructor."); +#endif + + throw new InvalidOperationException(error.ToString()); + } + } + +#if NETFX_CORE /// /// Invokes an action asynchronously on the UI thread. /// @@ -104,6 +136,8 @@ namespace GalaSoft.MvvmLight.Threading public static DispatcherOperation RunAsync(Action action) #endif { + CheckDispatcher(); + #if NETFX_CORE return UIDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()); #else @@ -144,5 +178,13 @@ namespace GalaSoft.MvvmLight.Threading #endif #endif } + + /// + /// Resets the class by deleting the + /// + public static void Reset() + { + UIDispatcher = null; + } } } \ No newline at end of file diff --git a/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Test (NET35)/Threading/DispatcherHelperTest.cs b/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Test (NET35)/Threading/DispatcherHelperTest.cs index 74cc945..d1053ce 100644 --- a/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Test (NET35)/Threading/DispatcherHelperTest.cs +++ b/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Test (NET35)/Threading/DispatcherHelperTest.cs @@ -1,60 +1,17 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Windows.Controls; using System.Threading; +using System.Windows.Controls; using GalaSoft.MvvmLight.Threading; -using System.Windows; -using System.Windows.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace GalaSoft.MvvmLight.Test.Threading { [TestClass] - public class TestDispatcherHelper + public class DispatcherHelperTest { + private const string NewContent = "New content"; private Button _button; - [TestMethod] - public void TestDispatchingToUiThread() - { - _button = new Button - { - Content = "Content1" - }; - - var manualEvent = new ManualResetEvent(false); - - var thread = new Thread(() => - { - Thread.Sleep(500); - DispatcherHelper.CheckBeginInvokeOnUI(() => - { - AccessMethodOnUiThread(NewContent); - manualEvent.Set(); - }); - }); - - DispatcherHelper.Initialize(); - - thread.Start(); - - manualEvent.WaitOne(1000); - -#if SILVERLIGHT - // No way to verify that the button is correctly set - // in WPF, however the mere fact that we didn't get an exception - // is an indication of success. - DispatcherHelper.UIDispatcher.BeginInvoke(VerifyButtonNewContent); -#endif - } - - private void AccessMethodOnUiThread(string newContent) - { - _button.Content = newContent; - } - [TestMethod] public void TestDirectAccessToUiThread() { @@ -67,18 +24,124 @@ namespace GalaSoft.MvvmLight.Test.Threading DispatcherHelper.CheckBeginInvokeOnUI(() => _button.Content = NewContent); #if SILVERLIGHT - // No way to verify that the button is correctly set - // in WPF, however the mere fact that we didn't get an exception - // is an indication of success. + // No way to verify that the button is correctly set + // in WPF, however the mere fact that we didn't get an exception + // is an indication of success. Assert.AreEqual(NewContent, _button.Content); #endif } - private const string NewContent = "New content"; + [TestMethod] + public void TestDispatchingToUiThread() + { + _button = new Button + { + Content = "Content1" + }; + + var manualEvent = new ManualResetEvent(false); + + var thread = new Thread( + () => + { + Thread.Sleep(500); + DispatcherHelper.CheckBeginInvokeOnUI( + () => + { + AccessMethodOnUiThread(NewContent); + manualEvent.Set(); + }); + }); + + DispatcherHelper.Initialize(); + + thread.Start(); + + manualEvent.WaitOne(1000); + +#if SILVERLIGHT + // No way to verify that the button is correctly set + // in WPF, however the mere fact that we didn't get an exception + // is an indication of success. + DispatcherHelper.UIDispatcher.BeginInvoke(VerifyButtonNewContent); +#endif + } + + [TestMethod] + public void TestFailingInitializationInCheckBeginInvokeOnUi() + { + DispatcherHelper.Reset(); + var done = false; + Exception receivedException = null; + + try + { + DispatcherHelper.CheckBeginInvokeOnUI( + () => + { + done = true; + }); + } + catch (Exception ex) + { + receivedException = ex; + } + + CheckException(receivedException); + Assert.IsFalse(done); + } + + [TestMethod] + public void TestFailingInitializationInRunAsync() + { + DispatcherHelper.Reset(); + var done = false; + Exception receivedException = null; + + try + { + DispatcherHelper.RunAsync( + () => + { + done = true; + }); + } + catch (Exception ex) + { + receivedException = ex; + } + + CheckException(receivedException); + Assert.IsFalse(done); + } + + private void AccessMethodOnUiThread(string newContent) + { + _button.Content = newContent; + } + + private void CheckException(Exception receivedException) + { + Assert.IsNotNull(receivedException); + Assert.AreEqual(typeof (InvalidOperationException), receivedException.GetType()); + +#if SILVERLIGHT +#if WINDOWS_PHONE + Assert.IsTrue(receivedException.Message.Contains("Call DispatcherHelper.Initialize() at the end of App.InitializePhoneApplication.")); +#else + Assert.IsTrue(receivedException.Message.Contains("Call DispatcherHelper.Initialize() in Application_Startup (App.xaml.cs).")); +#endif +#elif NETFX_CORE + Assert.IsTrue(receivedException.Message.Contains("Call DispatcherHelper.Initialize() at the end of App.OnLaunched.")); +#else + Assert.IsTrue( + receivedException.Message.Contains("Call DispatcherHelper.Initialize() in the static App constructor.")); +#endif + } private void VerifyButtonNewContent() { Assert.AreEqual(NewContent, _button.Content); } } -} +} \ No newline at end of file