问题

我有一个使用Caliburn.Micro作为MVVM框架和使用MEF进行“依赖注入(inject)”的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()方法分别显示和关闭相应的 View 。

错误

该代码在启动后台线程上的启动屏幕和启动动画有效的范围内似乎工作良好。但是,当代码返回到 bootstrap 时,该行
DisplayRootViewFor<IMainWindow>();

抛出带有以下消息的InvalidOperationException


堆栈跟踪为



尝试的解决方案

我试图将执行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。以上所有都引发相同的异常。

问题
  • 如何调用DisplayRootViewFor<IMainWindow>()方法并避免此异常?
  • 这种显示动画飞溅的方法是否合法?

  • 谢谢你的时间。

    编辑。我从很棒的Hans Passant https://stackoverflow.com/a/4078528/626442中找到了这个答案。有鉴于此,我尝试添加应用程序static App() { } ctor。
    static App()
    {
        // Other stuff.
        Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
    }
    

    但这(可能不足为奇)对我没有帮助。在同一位置有相同的异常(exception)...

    最佳答案

    您正在新的后台线程上创建SplashScreenView实例,但随后从Main UI线程在MetroThemeManager.ChangeAppStyle类内调用了ThemeManager

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

    由于ThemeManager.IsThemeChanged中的MetroWindow事件处理程序的代码无法在主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);
    

    关于c# - 显示SplashScreen导致InvalidOperationException,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43574586/

    10-11 15:52