使用 WPF / Prism 在应用程序退出/崩溃时处理对象实例

Posted

技术标签:

【中文标题】使用 WPF / Prism 在应用程序退出/崩溃时处理对象实例【英文标题】:Dispose object instance(s) on application exit/crash with WPF / Prism 【发布时间】:2021-10-10 05:37:55 【问题描述】:

我正在使用带有 DryIoc 的 WPF 应用程序的 prism 框架,但也许我不太了解它的某些用途。 (自学)或最佳实践。

我有一个遵循一次性模式的类库 (MyLibrary),因为它需要进行一些清理。 我有一个在其视图模型中使用MyLibrary 的模块。

如果应用程序关闭或什至应用程序崩溃,我想在这个库上调用 dispose,以防它此时尚未正确处理(否则它将使第三方应用程序在背景)

目前只有 1 个实例,但未来可能会超过 1 个。

到目前为止,这是我想出的:

当我的视图模型第一次需要 MyLibrary 时,它会注册一个实例,如下所示:

this.ce.RegisterInstance<MyLibrary>(this.myLibrary, "MyLibraryName"); 其中cePrism.Ioc.IContainerExtension 然后在我的 App.xaml.cs 中的protected override void OnExit(ExitEventArgs e) 方法中,我会这样做:this.Container.Resolve<MyLibrary>("MyLibraryName")?.Dispose();

这成功地处理了我的库对象(并按照我的预期关闭了第 3 方窗口),但是,这对我来说似乎有点不对劲(代码味道?)

有没有更好的方法呢?例如,我不觉得 App.xaml.cs 应该知道我的模块的视图模型提出的实例名称。有没有办法在没有名称字符串的情况下对容器中的所有 MyLibrary 类型调用 Dispose?

我希望我可以遍历容器中的所有 MyLibrarys 并调用 Dispose? 如果没有更好的方法,我想我可以接受。

但同样重要的是:我如何尝试以类似的方式对任何可能导致应用程序崩溃的未处理异常调用 Dispose?我可以以某种方式访问​​容器并处理任何MyLibrarys 吗? 我不打算尝试避免应用程序崩溃,只要确保 MyLibrary 调用其 Dispose 方法即可。


评论回复(字符过多):

单击 UI 按钮> 委托命令> 用 VM 中的方法编写的代码。 图书馆有启动(登录)的开销。

用户界面上有多个需要使用库的输入步骤。 在每个阶段(单击按钮)将其包装在 using 中并非不可能,但由于库启动开销,它效率低下。

我想:

- Instantiate library

- User inputs data, library does work, returns results, requires more input etc

- Several more of these data input stages / library work / results

- Dispose

问题:

- User exits application before library object is disposed

- How/Where to dispose?

尝试的解决方案:

- Use the container so that i can dispose in App.xaml.cs `OnExit`

- This solution works, but feels incorrect?

问题:在这种情况下,我可以做些什么更好/不同? 附加问题:如何在 prism 应用程序发生致命崩溃时处理此问题?

重要的是要处理掉它,这样库使用的 3rd 方应用程序的实例就不会在后台运行。

【问题讨论】:

你想做什么? 为什么视图模型要注册一个库?库实例首先来自哪里? 太多字符无法评论,无法解释。请参阅编辑中的评论回复。 @Haukinger 【参考方案1】:

好的,我现在将其更改为在 app.xaml.cs 中注册一个实例,我可以避免 .Resolve 调用。你会这样做吗?如果没有那怎么办?当它被注册为单例时,我不知道如何在没有 .Resolve 的情况下将其放入 OnExit

现在我的 app.xaml.cs 的缩减/简化示例:

namespace MyNamespace

    public partial class App
    
        public App()
        
            // Crash handling
            this.Dispatcher.UnhandledException += this.OnDispatcherUnhandledException;
        
    
        // My library provider that contains a dictionary of library instances.
        public IMyProvider MyProvider  get; set;  = new MyProvider();

        // Dispose provider on crash
        public void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        
            this.MyProvider.Dispose();
            MessageBox.Show("Unhandled exception occurred: \n" + e.Exception.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        

        protected override Window CreateShell()
        
            return this.Container.Resolve<MainWindow>();
        

        // Register as an instance?
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        
            containerRegistry.RegisterInstance<IMyProvider>(this.MyProvider);
        

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        
            // Add modules here...
        

        protected override void OnExit(ExitEventArgs e)
        
            base.OnExit(e);

            // Dispose my provider (and all contained instances in its dictionary)
            this.MyProvider.Dispose();
        
    

【讨论】:

【参考方案2】:

评论字数限制让我不能再写太多了。

@Haukinger:tl;dr:你让我走上了正轨。

我并没有完全按照您的示例进行操作(不使用 Lazy,库没有界面),但是您让我走上了正确的道路。 总的来说,我很难理解:DI、IoC、接口以及它们之间的关系。

你关于滥用容器的说法让我更仔细地观察它。 首先,我从视图模型中删除了所有 containerExtension.resolve 的实例,而是像往常一样注入到构造函数中。

我创建了一个类似于您建议的提供程序,并为我的库创建了一个提供程序接口。 提供者实现保留一个命名库实例的字典,以防我需要多个。 我仍然在应用程序的OnExit 中调用this.Container.Resolve&lt;IMyProvider&gt;().Dispose();,但感觉更好,因为我不必再命名实例了。 不过,仅仅拥有一个提供者接口并不能阻止任何人自己新建库实例吗?

库接口注册如下: containerRegistry.RegisterSingleton&lt;IMyProvider, MyProvider&gt;();

除了我的图书馆: 我目前正在为容器中的应用程序主题等一些事情注册一些应用程序范围的实例,如下所示:containerRegistry.RegisterInstance&lt;MyThing&gt;(this.MyThing); 这里仍然没有使用接口。仍在尝试理解它们,但容器中主题的应用程序范围实例运行良好。

从我看到IAnimal 接口可以有DogCat 实现的示例中,我的对象没有多个实现要求。 (除了我还没有尝试学习的单元测试,我认为在这种情况下我会从接口中受益)我只需要从不同的模块/程序集访问一些东西,并且正在使用容器来这样做。

我最大的问题之一是我有参数来构造一些实例,比如我的库,并且这些参数直到运行时才知道,所以当应用程序启动时我无法在 app.xaml.cs 中注册实例。

如果我必须使用仅在运行时才知道的参数(例如凭证)来构造一些东西,我是否需要遵循一种带有接口的工厂模式才能注册它们,或者我错过了什么? 我想我可以在创建容器扩展时注册一个实例(在程序集 A 的视图模型 A 中)?但是,如果我希望它们在不同的模块(装配 B 的视图模型 X)中,我需要稍后使用 .Resolve? 哪个感觉不对,为简单的凭证对象创建工厂和工厂接口感觉有点过头了?

我认为我只是没有完全理解接口,因为我经常看到的示例没有实现的参数,或者当我只有 1 个实现时,他们专注于讨论不同实现的好处。 或者示例与 IoC 或 DI 无关。

就处理应用程序崩溃事件而言,我执行了以下操作:

public App()

    this.Dispatcher.UnhandledException += this.OnDispatcherUnhandledException;


public void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)

    // Dispose provider
    this.Container.Resolve<IMyProvider>().Dispose();

    // Unhandled exception messagebox:
    MessageBox.Show("Unhandled exception occurred: \n" + e.Exception.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);

我还在为接口/工厂模式/DI/Ioc/通用设计而烦恼,但你让我走上了正确的道路。 我更好地将东西注入构造函数,我可以使用提供程序管理我的库实例,并在退出和崩溃时处理。

谢谢。

【讨论】:

你永远不应该打电话给Resolve,也就是说,至少99.9%的时间。在某些用例中调用它实际上是一个好主意,但它们很少见。您希望您的第一次调用完成所有工作 (Resolve&lt;MainWindowViewModel&gt;),您的代码甚至都不应该知道您正在使用容器。 @Haukinger,我添加了一个新答案,其中包含我的 app.xaml.cs 的缩减/简化示例,该示例通过将MyProvider 的实例注册到IMyProvider 来删除解析调用,如果这不正确,那么我完全不知道如何(正确地)实现我想要的。【参考方案3】:

我建议你不要尝试在这里滥用容器。

相反,您可以自己为您的库创建一个管理器,并在关闭时清理它:

public interface ILibraryProvider : IDisposable

    ILibrary GetLibrary();


internal class LibraryProvider : ILibraryProvider

    public LibraryProvider( Lazy<ILibrary> library )
    
        _library = library;
    

    #region ILibraryProvider
    public ILibrary GetLibrary() => _library.Value;

    public void Dispose()
    
        if (_library.IsValueCreated)
            _library.Value.Dispose();
    
    #endregion

    #region private
    private readonly Lazy<ILibrary> _library;
    #endregion

Application.OnExit 中,处置LibraryManager

如果您需要多个不同的库,请将字符串参数添加到 Get,注入 Func 而不是 Lazy,并将实例存储在 Dictionary 中。

【讨论】:

以上是关于使用 WPF / Prism 在应用程序退出/崩溃时处理对象实例的主要内容,如果未能解决你的问题,请参考以下文章

在 Wpf 应用程序中使用 Prism 进行导航

Prism完成的一个WPF项目

无法使用 PRISM 5、MVVM 和 EF 6 在 WPF 中刷新 DataGrid

WPF Step By Step 系列5-Prism框架在项目中使用

WPF Step By Step 系列5-Prism框架在项目中使用

02Prism WPF 入门实战 - 建项