问题
我有一个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
调用线程无法访问此对象,因为其他线程拥有该对象。
堆栈跟踪是
在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,如下所示
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 https://stackoverflow.com/a/4078528/626442 )那里发现了这个答案。有鉴于此,我尝试添加应用程序static App() { } ctor。
static App()
{
// Other stuff.
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
}但(可能并不令人意外)这对我没有帮助。同一地点同样的例外..。
发布于 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。
注释掉两行代码将消除冲突,请参见下面的行。但是很明显,实际的解决方案需要在另一个层次上完成,见上文。
// 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);https://stackoverflow.com/questions/43574586
复制相似问题