问题
我有一个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。以上所有都抛出相同的异常。
问题
如何调用DisplayRootViewFor<IMainWindow>()
方法并避免此异常?
这种显示动画效果的方法是合法的吗?
感谢您的时间。
编辑。我从令人敬畏的Hans Passant https://stackoverflow.com/a/4078528/626442中找到了这个答案。鉴于此,我尝试添加应用程序static App() { }
ctor。
static App()
{
// Other stuff.
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
}
但(可能不足为奇)这对我没有帮助。在同一地点有相同的例外......
答案 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