显示SplashScreen导致InvalidOperationException

时间:2017-04-23 17:56:18

标签: c# wpf mvvm mef caliburn.micro

问题

我有一个MVVM应用程序,它使用Caliburn.Micro作为MVVM框架和MEF用于"依赖注入" (在引号中,因为我知道它不是严格意义上的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

  

调用线程无法访问此对象,因为另一个线程拥有它。

堆栈跟踪

  

在位于d:\ projects \ git \ MahApps.Metro的MahApps.Metro.Controls.MetroWindow.get_Flyouts()的System.Windows.DependencyObject.GetValue(DependencyProperty dp)上的System.Windows.Threading.Dispatcher.VerifyAccess()处\ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs:位于d:\ projects \ git \ MahApps.Metro \的MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object sender,OnThemeChangedEventArgs e)第269行src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ MetroWindow.cs:System.EventHandler1.Invoke中的第962行(对象发送者,TEventArgs e)        在MahApps.Metro.Controls.SafeRaise.Raise [T](EventHandler1 eventToRaise,Object sender,T args)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ Controls \ SafeRaise .cs:第26行在MahApps.Metro.ThemeManager.OnThemeChanged(Accent newAccent,AppTheme newTheme)中的d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager \ ThemeManager.cs: MahApps.Metro.ThemeManager.ChangeAppStyle(ResourceDictionary资源,Tuple`2 oldThemeInfo,Accent newAccent,AppTheme newTheme)的第591行在d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager中\ ThemeManager.cs:位于d:\ projects \ git \ MahApps.Metro \ src \ MahApps.Metro \ MahApps.Metro.Shared \ ThemeManager的MahApps.Metro.ThemeManager.ChangeAppStyle(应用程序应用程序,Accent newAccent,AppTheme newTheme)第407行\ ThemeManager.cs:Augur.Core.Themes.ThemeManager中的第345行。文件主题(字符串名称)在F:\ Camus \ Augur \ Src \ Augur \ Core \ Themes \ ThemeManager.cs:Augur.Modules.Shell中的第46行。 ViewMod在F:\ Camus \ Augur \ Src \ Augur \ Modules \ Shell \ ViewModels \ ShellViewModel.cs中的els.ShellViewModel.OnViewLoaded(对象视图):Caliburn.Micro.XamlPlatformProvider中的第73行。&lt;&gt; c__DisplayClass11_0.b__0(对象s) ,RoutedEventArgs e)位于System.Windows.UIElement的System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)中的Caliburn.Micro.View。&lt;&gt; c__DisplayClass8_0.b__0(Object s,RoutedEventArgs e)。 RaiseEventImpl(DependencyObject的发件人,RoutedEventArgs参数)在System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject的根,RoutedEvent routedEvent)在System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(对象根)在MS.Internal.LoadedOrUnloadedOperation.DoWork()在System.Windows。在System.Windows.Media.MediaContext.RenderMe的System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)的System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()中的Media.MediaContext.FireLoadedPendingCallbacks() System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd)上System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg,IntPtr wparam,IntPtr lparam)的System.Windows.Interop.HwndTarget.OnResize()中的ssageHandler(Object resizedCompositionTarget) ,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&amp;处理)在MS.Win32.HwndWrapper.WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&amp; handling)处于System.Windows.Threading.ExceptionWrapper.InternalRealCall的MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)处()在System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority,TimeSpan)的System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source,Delegate callback,Object args,Int32 numArgs,Delegate catchHandler)上委托回调,Object args,Int32 numArgs)在MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam)上的timeout,Delegate方法,Object args,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. 这种显示动画效果的方法是合法的吗?

  3. 感谢您的时间。

    编辑。我从令人敬畏的Hans Passant https://stackoverflow.com/a/4078528/626442中找到了这个答案。鉴于此,我尝试添加应用程序static App() { } ctor。

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

    但(可能不足为奇)这对我没有帮助。在同一地点有相同的例外......

1 个答案:

答案 0 :(得分:4)

您正在新的后台线程上创建SplashScreenView的实例,但之后在主UI线程中调用MetroThemeManager.ChangeAppStyle类中的ThemeManager

由于MetroWindow类是您SplashScreenView的父级,因此您无法通过在{{1}内调用ThemeManager.IsThemeChanged来阻止内部订阅您正在触发的MetroThemeManager.ChangeAppStyle事件} class。

由于ThemeManager内的ThemeManager.IsThemeChanged事件处理程序的代码无法在主UI空间上执行,该线程与创建的线程启动屏幕不同,您应该[1]派生您的{{1来自标准MetroWindow类,以避免依赖SplashScreenView或[2],避免在Window仍然有效时调用MetroWindow

注释掉两行代码就是删除违规行为,请参阅下面的行。但显然,实际的解决方案需要在另一个层面上完成,见上文。

MetroThemeManager.ChangeAppStyle