首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >显示导致SplashScreen的InvalidOperationException

显示导致SplashScreen的InvalidOperationException
EN

Stack Overflow用户
提问于 2017-04-23 17:56:17
回答 1查看 729关注 0票数 1

问题

我有一个MVVM应用程序,它使用Caliburn.Micro作为MVVM框架,MEF用于“依赖注入”(如我所知,它不是一个严格意义上的DI容器)。这个大型应用程序的合成过程开始花费越来越多的时间,基于MEF在应用程序启动期间所做的组合的数量,因此我想使用一个动画启动屏幕。

下面我将概述我的当前代码,该代码显示单独线程上的启动屏幕并尝试启动主应用程序。

代码语言:javascript
复制
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实现是

代码语言:javascript
复制
[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()方法分别显示和关闭相应的视图。

误差

这段代码似乎运行良好,因为它在后台线程上启动启动屏幕,启动动画作品等等。但是,当代码返回引导程序时,行。

代码语言:javascript
复制
DisplayRootViewFor<IMainWindow>(); 

引发带有以下消息的InvalidOperationException

调用线程无法访问此对象,因为其他线程拥有该对象。

堆栈跟踪是

在System.Windows.Threading.Dispatcher.VerifyAccess() at 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:line 269在MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object发件人,OnThemeChangedEventArgs e在d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\MetroWindow.cs:line 962在System.EventHandler1. in (对象发送方,( MahApps.Metro.Controls.SafeRaise.RaiseT in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\SafeRaise.cs:line 26 at MahApps.Metro.ThemeManager.OnThemeChanged(Accent newAccent,AppTheme newTheme)在d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 591 at MahApps.Metro.ThemeManager.ChangeAppStyle(ResourceDictionary resources,Tuple`2‘2 oldThemeInfo,AppTheme newAccent,d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 407 at MahApps.Metro.ThemeManager.ChangeAppStyle(Application app,AppTheme newTheme(重音newAccent )( d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 345 at Augur.Core.Themes.ThemeManager.SetCurrentTheme(String name) F:\Camus\Augur\Src\Augur\Core\Themes\ThemeManager.cs:line 46 at Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(Object view) ( F:\Camus\Augur\Src\Augur\Modules\Shell\ViewModels\ShellViewModel.cs:line 73 at Caliburn.Micro.XamlPlatformProvider.<>c_ )_DisplayClass11_0.b__0(Object s,在Caliburn.Micro.View.<>c__DisplayClass8_0.b__0(Object s,RoutedEventArgs e)在System.Windows.EventRoute.InvokeHandlersImpl(Object源,RoutedEventArgs args,Boolean (在System.Windows.UIElement.RaiseEventImpl(DependencyObject发件人,RoutedEventArgs args)在System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject根,( System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root) )在MS.Internal.LoadedOrUnloadedOperation.DoWork() at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks() at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget( System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget) at System.Windows.Interop.HwndTarget.OnResize() at System.Windows.Interop.HwndTarget.OnResize() at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg,IntPtr wParam,IntPtr lParam ) at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd,Int32 msg,IntPtr IntPtr,,在MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o的MS.Win32.HwndWrapper.WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean& handled)上的System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate回调,对象args,Int32 numArgs在System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object源,委托回调,对象args,Int32 numArgs,委托发展)在System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority优先级,TimeSpan超时值,委托方法,对象args,numArgs)在( hwnd,msg,,IntPtr lParam)

尝试解决方案

我试图将执行DisplayRootViewFor<IMainWindow>();的代码更改为使用dispatcher,如下所示

代码语言:javascript
复制
base.OnStartup(sender, suea);
Application.Current.Dispatcher.Invoke(() => DisplayRootViewFor<IMainWindow>()); // Still throws the same exception.

代码语言:javascript
复制
base.OnStartup(sender, suea);
Application.Current.Dispatcher.BeginInvoke(
    new System.Action(delegate { DisplayRootViewFor<IMainWindow>(); })); // Still throws the same exception.

甚至

代码语言:javascript
复制
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 https://stackoverflow.com/a/4078528/626442 )那里发现了这个答案。有鉴于此,我尝试添加应用程序static App() { } ctor。

代码语言:javascript
复制
static App()
{
    // Other stuff.
    Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
}

但(可能并不令人意外)这对我没有帮助。同一地点同样的例外..。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-04-27 21:36:39

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

因为MetroWindow类是您的SplashScreenView的父类,所以不能阻止它在内部订阅ThemeManager.IsThemeChanged事件,这是通过在ThemeManager类中调用MetroThemeManager.ChangeAppStyle来触发的。

由于ThemeManager.IsThemeChanged事件处理程序在MetroWindow中的代码不能在主UI线程上执行,这与线程启动屏幕不同,所以您应该从标准的Window类派生SplashScreenView以避免对MetroWindow的依赖,或者2在SplashScreenView还活着时避免调用MetroThemeManager.ChangeAppStyle

注释掉两行代码将消除冲突,请参见下面的行。但是很明显,实际的解决方案需要在另一个层次上完成,见上文。

代码语言:javascript
复制
// 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);
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43574586

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档