【发布时间】:2017-09-20 08:06:36
【问题描述】:
问题
我有一个 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() 在 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: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) 在 d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\Controls\SafeRaise 中的 MahApps.Metro.Controls.SafeRaise.Raise[T](EventHandler1 eventToRaise, Object sender, T args) .cs: d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager\ThemeManager.cs 中 MahApps.Metro.ThemeManager.OnThemeChanged(Accent newAccent, AppTheme newTheme) 的第 26 行: d:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager 中 MahApps.Metro.ThemeManager.ChangeAppStyle(ResourceDictionary resources, Tuple`2 oldThemeInfo, Accent newAccent, AppTheme newTheme) 的第 591 行\ThemeManager.cs:D:\projects\git\MahApps.Metro\src\MahApps.Metro\MahApps.Metro.Shared\ThemeManager 中 MahApps.Metro.ThemeManager.ChangeAppStyle(Application app, Accent newAccent, AppTheme newTheme) 的第 407 行\ThemeManager.cs:Augur.Core.Themes.ThemeManager.SetCurrentTheme(字符串名称)的第 345 行在 F:\Camus\Augur\Src\Augur\Core\Themes\ThemeManager.cs:Augur.Modules.Shell 的第 46 行。视图模块els.ShellViewModel.OnViewLoaded(Object view) in F:\Camus\Augur\Src\Augur\Modules\Shell\ViewModels\ShellViewModel.cs:Caliburn.Micro.XamlPlatformProvider 的第 73 行。c__DisplayClass11_0.b__0(Object s, RoutedEventArgs e) 在 Caliburn.Micro.View.c__DisplayClass8_0.b__0(Object s, RoutedEventArgs e) 在 System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) 在 System.Windows.UIElement.RaiseEventImpl(DependencyObject sender , RoutedEventArgs args) 在 System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent) 在 System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root) 在 MS.Internal.LoadedOrUnloadedOperation.DoWork() 在 System.Windows.Media.MediaContext。 FireLoadedPendingCallbacks() 在 System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() 在 System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget) 在 System.Windows.Media.MediaContext.RenderMessageHandler (Object resizedCompositionTarget) 在 System.Windows.Interop.HwndTarget.OnResize() 在 System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam) 在 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) 在 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) 在 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() { }ctor。
static App()
{
// Other stuff.
Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };
}
但是(可能不出所料)这对我没有帮助。相同位置的相同异常...
【问题讨论】:
-
看起来 Splash show 正在访问 UI 线程。我们可以试试 Dispatcher.Invoke(()=>{splashScreen.Show();});在 DoShowSplashScreen() 方法中
-
您的初始屏幕代码看起来很奇怪。它与 Application.Current 一起使用,它设置了 SynchronizationContext 一次就永远不会改变它?否则,你有一个小的复制项目吗?为什么不使用标准 WPF 的 SplashScreen ?来源甚至可以得到灵感:referencesource.microsoft.com/#WindowsBase/Base/System/Windows/…
-
嗨@Simsons 我试过这个。这没有帮助,我得到了同样的例外。
-
@SimonMourier 感谢您抽出宝贵时间。我只为
splashThread设置SynchonizationContext。SynchronizationContext仅附加到当前线程 (codeproject.com/Articles/31971/…)。我错过了什么吗?正如我上面所说,标准 WPF 启动画面只允许显示静态图像,我想在动画启动画面上显示加载信息。如果您愿意提供帮助,我可以压缩我拥有的代码并通过 OneDrive 提供? -
@MoonKnight - 只要我们可以轻松复制就可以了
标签: c# wpf mvvm mef caliburn.micro