显示闪屏曹景伟出现InvalidOperationException

显示闪屏曹景伟出现InvalidOperationException

问题描述:

问题显示闪屏曹景伟出现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://*.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

您正在新后台线程上创建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方面的最佳设计决定,如果需要很难覆盖。 –