问题
我有一个 MVVM 应用程序,它使用 Caliburn.Micro 作为 MVVM 框架,并使用 MEF 进行“依赖注入(inject)”(在引号中我知道它不是严格意义上的 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
带有以下消息
The calling thread cannot access this object because a different thread owns it.
堆栈跟踪是
at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.GetValue(DependencyProperty dp) at MahApps.Metro.Controls.MetroWindow.get_Flyouts() in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\MetroWindow.cs:line 269 at MahApps.Metro.Controls.MetroWindow.ThemeManagerOnIsThemeChanged(Object sender, OnThemeChangedEventArgs e) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\MetroWindow.cs:line 962 at System.EventHandler1.Invoke(Object sender, TEventArgs e) at MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler1 eventToRaise, Object sender, T args) 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) in 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 oldThemeInfo, Accent newAccent, AppTheme newTheme) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 407 at MahApps.Metro.ThemeManager.ChangeAppStyle(Application app, Accent newAccent, AppTheme newTheme) in d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs:line 345 at Augur.Core.Themes.ThemeManager.SetCurrentTheme(String name) in F:\Camus\Augur\Src\Augur\Core\Themes\ThemeManager.cs:line 46 at Augur.Modules.Shell.ViewModels.ShellViewModel.OnViewLoaded(Object view) in F:\Camus\Augur\Src\Augur\Modules\Shell\ViewModels\ShellViewModel.cs:line 73 at Caliburn.Micro.XamlPlatformProvider.<>c__DisplayClass11_0.b__0(Object s, RoutedEventArgs e) at Caliburn.Micro.View.<>c__DisplayClass8_0.b__0(Object s, RoutedEventArgs e) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent) at System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root) at MS.Internal.LoadedOrUnloadedOperation.DoWork() at System.Windows.Media.MediaContext.FireLoadedPendingCallbacks() at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget) at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget) 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 wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
尝试的解决方案
我试图更改执行 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() { }
构造器。
static App()
{
// Other stuff.
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
}
但是(可能不足为奇)这对我没有帮助。相同位置的相同异常...
最佳答案
您正在新的后台线程上创建 SplashScreenView
的实例,然后从主 UI 调用 ThemeManager
类中的 MetroThemeManager.ChangeAppStyle
线程。
因为 MetroWindow
类是您的 SplashScreenView
的父类,您无法阻止它在内部订阅您触发的 ThemeManager.IsThemeChanged
事件在 ThemeManager
类中调用 MetroThemeManager.ChangeAppStyle
。
由于 MetroWindow
中的 ThemeManager.IsThemeChanged
事件处理程序的代码无法在主 UI 线程上执行,这不同于创建线程启动画面时,您应该[1] 从标准 Window
类派生您的 SplashScreenView
以避免依赖于 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# - 显示启动画面导致 InvalidOperationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43574586/