2017-04-23 54 views
1

問題顯示閃屏曹景偉出現InvalidOperationException

我有一個使用Caliburn.Micro作爲MVVM框架和MEF的「依賴注入」一個MVVM應用程序(在引號,因爲我知道它不是嚴格意義上的DI容器) 。根據MEF在應用程序啓動過程中正在執行的構圖數量,此大型應用程序的構圖過程開始花費越來越多的時間,因此我想使用動畫啓動畫面。

下面我將概述我當前的代碼顯示在一個單獨的線程啓動畫面,並嘗試啓動主要應用

public class Bootstrapper : BootstrapperBase 
{ 
    private List<Assembly> priorityAssemblies; 
    private ISplashScreenManager splashScreenManager; 

    public Bootstrapper() 
    { 
     Initialize(); 
    } 

    protected override void Configure() 
    { 
     var directoryCatalog = new DirectoryCatalog(@"./"); 
     AssemblySource.Instance.AddRange(
      directoryCatalog.Parts 
        .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly) 
        .Where(assembly => !AssemblySource.Instance.Contains(assembly))); 

     priorityAssemblies = SelectAssemblies().ToList(); 
     var priorityCatalog = new AggregateCatalog(priorityAssemblies.Select(x => new AssemblyCatalog(x))); 
     var priorityProvider = new CatalogExportProvider(priorityCatalog); 

     var mainCatalog = new AggregateCatalog(
      AssemblySource.Instance 
       .Where(assembly => !priorityAssemblies.Contains(assembly)) 
       .Select(x => new AssemblyCatalog(x))); 
     var mainProvider = new CatalogExportProvider(mainCatalog); 

     Container = new CompositionContainer(priorityProvider, mainProvider); 
     priorityProvider.SourceProvider = Container; 
     mainProvider.SourceProvider = Container; 

     var batch = new CompositionBatch(); 

     BindServices(batch); 
     batch.AddExportedValue(mainCatalog); 

     Container.Compose(batch); 
    } 

    protected virtual void BindServices(CompositionBatch batch) 
    { 
     batch.AddExportedValue<IWindowManager>(new WindowManager()); 
     batch.AddExportedValue<IEventAggregator>(new EventAggregator()); 
     batch.AddExportedValue(Container); 
     batch.AddExportedValue(this); 
    } 


    protected override object GetInstance(Type serviceType, string key) 
    { 
     String contract = String.IsNullOrEmpty(key) ? 
      AttributedModelServices.GetContractName(serviceType) : 
      key; 
     var exports = Container.GetExports<object>(contract); 

     if (exports.Any()) 
      return exports.First().Value; 

     throw new Exception(
      String.Format("Could not locate any instances of contract {0}.", contract)); 
    } 

    protected override IEnumerable<object> GetAllInstances(Type serviceType) 
    { 
     return Container.GetExportedValues<object>(
      AttributedModelServices.GetContractName(serviceType)); 
    } 

    protected override void BuildUp(object instance) 
    { 
     Container.SatisfyImportsOnce(instance); 
    } 

    protected override void OnStartup(object sender, StartupEventArgs suea) 
    { 
     splashScreenManager = Container.GetExportedValue<ISplashScreenManager>(); 
     splashScreenManager.ShowSplashScreen(); 

     base.OnStartup(sender, suea); 
     DisplayRootViewFor<IMainWindow>(); // HERE is the Problem line. 

     splashScreenManager.CloseSplashScreen(); 
    } 

    protected override IEnumerable<Assembly> SelectAssemblies() 
    { 
     return new[] { Assembly.GetEntryAssembly() }; 
    } 

    protected CompositionContainer Container { get; set; } 

    internal IList<Assembly> PriorityAssemblies 
    { 
     get { return priorityAssemblies; } 
    } 
} 

ISplashScreenManager實現

[Export(typeof(ISplashScreenManager))] 
[PartCreationPolicy(CreationPolicy.NonShared)] 
public class SplashScreenManager : ISplashScreenManager 
{ 
    private ISplashScreenViewModel splashScreen; 
    private Thread splashThread; 
    private Dispatcher splashDispacher; 

    public void ShowSplashScreen() 
    { 
     splashDispacher = null; 
     if (splashThread == null) 
     { 
      splashThread = new Thread(new ThreadStart(DoShowSplashScreen)); 
      splashThread.SetApartmentState(ApartmentState.STA); 

      splashThread.IsBackground = true; 
      splashThread.Name = "SplashThread"; 

      splashThread.Start(); 
      Log.Trace("Splash screen thread started"); 

      Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; 
      Application.Current.MainWindow = null; 
     } 
    } 

    private void DoShowSplashScreen() 
    { 
     splashScreen = IoC.Get<ISplashScreenViewModel>(); 

     splashDispacher = Dispatcher.CurrentDispatcher; 
     SynchronizationContext.SetSynchronizationContext(
      new DispatcherSynchronizationContext(splashDispacher)); 

     splashScreen.Closed += (s, e) => 
      splashDispacher.BeginInvokeShutdown(DispatcherPriority.Background); 
     splashScreen.Show(); 

     Dispatcher.Run(); 
     Log.Trace("Splash screen shown and dispatcher started"); 
    } 

    public void CloseSplashScreen() 
    { 
     if (splashDispacher != null) 
     { 
      splashDispacher.BeginInvokeShutdown(DispatcherPriority.Send); 
      splashScreen.Close(); 
      Log.Trace("Splash screen close requested"); 
     } 
    } 

    public ISplashScreenViewModel SplashScreen 
    { 
     get { return splashScreen; } 
    } 
} 

其中ISplashScreenViewModel.Show()ISplashScreenViewModel.Close()方法分別顯示和關閉相應的視圖。

錯誤

此代碼似乎只要它啓動在後臺線程啓動畫面和飛濺的動漫作品等。然而,當代碼返回引導程序行以及工作

DisplayRootViewFor<IMainWindow>(); 

拋出InvalidOperationException以下消息

調用線程不能訪問此OBJ因爲不同的線程擁有它。

堆棧跟蹤是

在System.Windows.Threading.Dispatcher.VerifyAccess()在System.Windows.DependencyObject.GetValue(的DependencyProperty DP)在MahApps.Metro.Controls.MetroWindow.get_Flyouts ()在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs中:第269行MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object sender,OnThemeChangedEventArgs e )在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs中:在System.EventHandler1.Invoke中的第962行(對象發件人,TEventArgs e) 位於MahApps。 Metro.Controls.SafeRaise在MahApps中的第26行:.Raise [T](EventHandler1 eventToRaise,Object sender,T args)在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ SafeRaise.cs中。 Metro.ThemeManager.OnThemeChanged(Accent newAccent,AppTheme newTheme)在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs中:第591行,位於MahApps.Metro.ThemeManager .ChangeAppStyle(ResourceDictionary資源,Tuple`2 oldThemeInfo,Accent newAccent,AppTheme newTheme)在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs中:第407行MahApps.Metro.ThemeManager.ChangeAppStyle(Application application,Accent newAccent,AppTheme newTheme)in d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs:line 345 at在Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(Obj)中的第46行, F:\ Camus \ Augur \ Src \ Augur \ Modules \ Shell \ ViewModels \ ShellViewModel.cs:Caliburn.Micro.XamlPlatformProvider的第73行。在Caliburn.Micro.View上的c__DisplayClass11_0.b__0(Object s,RoutedEventArgs e)。 <> c_DisplayClass8_0.b__0(Object s,RoutedEventArgs e)在System.Windows.EventRoute。在System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root,RoutedEvent routedEvent)System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,RoutedEventArgs args))上的InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)對象根)在System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()在System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()在System.Windows.Media.MediaContext.RenderMessageHandlerCore(對象resizedCompositionTarget)在MS.Internal.LoadedOrUnloadedOperation.DoWork() )System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)System.Windows.Interop.HwndTarget.OnResize()System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg,IntPtr wparam,IntPtr lparam)在系統。 Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,布爾型(System.Windows.Threading.ExceptionWrapper.InternalRealCall)上的MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)處的MS.Win32.HwndWrapper.WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,布爾&已處理) (System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority,System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source,Delegate callback,Object args,Int32 numArgs,Delegate catchHandler)時間跨度超時,代表方法中,在MS.Win32.HwndSubclass.SubclassWndProc(IntPtr的HWND,MSG的Int32,IntPtr的wParam中,IntPtr的LPARAM)對象指定參數時,的Int32 numArgs)

嘗試的解決方案

我試圖改變執行DisplayRootViewFor<IMainWindow>();如下

base.OnStartup(sender, suea); 
Application.Current.Dispatcher.Invoke(() => DisplayRootViewFor<IMainWindow>()); // Still throws the same exception. 

base.OnStartup(sender, suea); 
Application.Current.Dispatcher.BeginInvoke(
    new System.Action(delegate { DisplayRootViewFor<IMainWindow>(); })); // Still throws the same exception. 

甚至

TaskScheduler guiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

splashScreenManager = Container.GetExportedValue<ISplashScreenManager>(); 
splashScreenManager.ShowSplashScreen(); 

Task.Factory.StartNew(() => 
{ 
    base.OnStartup(sender, suea); 
    DisplayRootViewFor<IMainWindow>(); 
}, CancellationToken.None, 
    TaskCreationOptions.None, 
    guiScheduler); 

試圖強制使用的使用調度代碼Gui MainThread。以上所有都會拋出同樣的異常。

問題

  1. 我如何可以調用DisplayRootViewFor<IMainWindow>()方法,避免此異常?

  2. 這種顯示動畫效果的方法是合法嗎?

謝謝你的時間。


編輯。我已經從令人敬畏的Hans Passant https://stackoverflow.com/a/4078528/626442發現了這個答案。鑑於此,我試圖添加應用程序static App() { } ctor。

static App() 
{ 
    // Other stuff. 
    Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { }; 
} 

但(可能是意料之中),這並沒有幫助我。在同一地點也有同樣的例外...

+1

看起來像Splash show正在訪問UI線程。我們可以嘗試Dispatcher.Invoke(()=> {splashScreen.Show();});在DoShowSplashScreen()方法中 – Simsons

+1

你的啓動畫面代碼看起來很奇怪。它使用Application.Current,它設置SynchronizationContext一次永遠不會改回它?否則,你有一個小的複製項目?你爲什麼不使用標準的WPF的SplashScreen?源甚至可以得到靈感:https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/SplashScreen.cs,9c43c23f03d92271 –

+0

嗨@Simsons我試過這個。這沒有幫助,我得到同樣的例外。 – MoonKnight

回答

4

您正在新後臺線程上創建SplashScreenView的實例,但在主UI線程中調用MetroThemeManager.ChangeAppStyleThemeManager類。

因爲MetroWindow類是你SplashScreenView你不能阻止它的內部預訂至ThemeManager.IsThemeChanged事件,您可以通過調用MetroThemeManager.ChangeAppStyleThemeManager類觸發父。

由於內部MetroWindowThemeManager.IsThemeChanged事件處理程序的代碼不能在主UI線程是從線程閃屏不同的是創造了你應該爲[1]派生SplashScreenView從標準Window類,以避免依賴執行MetroWindow或[2]避免撥打MetroThemeManager.ChangeAppStyle而您的SplashScreenView仍然存在。

註釋掉兩行代碼是刪除違規行爲,請參閱下面的行。但顯然,實際的解決方案需要在另一個層面上完成,見上文。

// this line is causing violation due to hidden dependency 
MetroThemeManager.ChangeAppStyle(Application.Current, metroAccent, metroTheme); 

// this line is causing an error when closing the splash screen 
splashDispacher.BeginInvokeShutdown(DispatcherPriority.Send); 
+0

偉大的工作。發現。應該看到一個公平的......這是最近才添加的代碼。 – MoonKnight

+0

我很高興它有幫助,但間接依賴關係很難發現,但imho內部處理Loaded事件並不是Metro方面的最佳設計決定,如果需要很難覆蓋。 –