如何在MvvmCross/WPF应用程序中处理临时文件清理

huangapple go评论60阅读模式
英文:

How to to handle temporary files cleanup in MvvmCross/WPF application

问题

我的基于MvvmCross的桌面应用程序(Windows/WPF + MacOS/Cocoa)在正常运行期间创建一些临时文件。这些文件是在async命令内创建的,并在命令结束之前删除。

然而,由于命令是async的,用户可以在命令完成之前通过在主窗口上点击红色关闭按钮来关闭应用程序。在这种情况下,命令将无法完成其工作,临时文件也不会被清理。

该应用程序由一个单一的View(派生自MvxWpfView)和相应的ViewModel 组成。可以使用TipCalc示例(https://github.com/MvvmCross/MvvmCross-Samples/tree/master/TipCalc)作为起点。

我需要以某种方式处理应用程序的关闭,并等待所有async操作完成。

到目前为止,我已经尝试过的方法(类名如上述的TipCalc示例中所示):

  • TipViewModel视图模型和TipView视图实现IDisposableIAsyncDisposable - Dispose & DisposeAsync根本没有被调用。
  • TipViewModel视图模型中重写ViewDestroy - 也没有被调用。
  • TipView本身内部订阅TipView.Unload事件 - 事件处理程序也没有被调用。
  • 检查要重载或实现到MvxWpfSetupIMvxAppStart中的相关方法 - 没有找到相关内容。
  • 尝试在Window级别处理Window.Close事件,然后将调用传递到ViewMvxWpfView)或ViewModel,但无法找到以正常同步或正确等待异步操作完成的常规方法。

那么,我该如何处理应用程序的关闭并清理我的临时文件?

英文:

My MvvmCross based desktop application (Windows/WPF + MacOS/Cocoa) creates some temporary files during its normal operation.
The files are created within the async command and deleted before the command ends.

However, because the command is async, the user can close the application before the command finishes by simply clicking the red cross in the main window. In this case the command will not complete its work and the temporary files will not be cleaned.

The application consists of a single View (derived from MvxWpfView) and a corresponding ViewModel. The TipCalc sample (https://github.com/MvvmCross/MvvmCross-Samples/tree/master/TipCalc) can be used as a starting point.

I need to somehow handle the closing of the application and wait for all async operations to finish.

What I have tried so far (class names given as in the TipCalc sample mentioned above):

  • implement IDisposable and IAsyncDisposable for view model TipViewModel and view TipView. Dispose & DisposeAsync just not called at all.
  • override ViewDestroy in the TipViewModel view model - it is also not called.
  • subscribe to the TipView.Unload event inside the TipView itself - event handler not called.
  • checked for relevant methods to overload or implement into the MvxWpfSetup and IMvxAppStart - nothing relevant found.
  • tried to handle Window.Close event on the Window level then transfer the call into the View (MvxWpfView) or ViewModel, but could not find the normal way to do everything synchronously or with correct waiting of the async operations.

So, how do I handle the app close and clean up my temporary files?

答案1

得分: 1

一种确定的方法是使用 FileOptions.DeleteOnClose 打开文件,即使程序结束,文件也将始终被删除。

convertedFileStream = new StreamReader(new FileStream(ConvertedFilepath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.DeleteOnClose));
英文:

One sure way is to open the file with FileOptions.DeleteOnClose, the file will always get deleted even if the program ends.

convertedFileStream = new StreamReader(new FileStream(ConvertedFilepath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.DeleteOnClose));

答案2

得分: 0

你可以从 Application.Exit 事件处理程序开始清理工作。

基本上,你有两种选项来跟踪或识别应用程序的临时文件:

  • 在特定子文件夹中创建所有临时文件。此子文件夹应位于用户的 Windows 临时文件夹中,以允许用户在你可能遗漏某些内容的情况下手动清理。
  • 创建一个全局的最近文件列表,例如在你的 App 类中存储所有临时文件路径。

然后在事件处理程序中,要么删除临时子文件夹,要么通过枚举全局的最近文件列表来删除每个单独的文件。

对于外部进程,你无法控制它们在哪里创建它们的临时文件。这些进程负责清理它们的生成物,就像你负责清理你的应用程序生成物一样。

为简单起见,以下示例使用静态属性使临时文件夹在全局范围内可用。
在高级场景中,例如当你编写单元测试并使用依赖注入时,你应该将管理临时目录的代码(创建和删除)移动到一个专门的类中,例如 TempFolderManager 类,然后将其作为共享实例注入到需要的应用程序中。

public partial class App : Application
{
    public string? ApplicationTempDirectoryFullName => this.ApplicationTempDirectory?.FullName;

    // 将实际的 DirectoryInfo 设为私有,以防止随机操作
    private DirectoryInfo ApplicationTempDirectory { get; set; }
    private const string TempFolderNamePrefix = "MyApplicationTemp";

    public App()
    {
        this.Exit += App_Exit;

        // 创建临时文件夹
        // 并将其公开为静态属性,以允许你的应用程序引用它
        // 例如,用于读取或创建临时文件。
        this.ApplicationTempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), App.TempFolderNamePrefix));
    }

    void App_Exit(object sender, ExitEventArgs e)
    {
        // 删除临时文件夹及其所有子项(递归删除)
        this.ApplicationTempDirectory.Delete(true);
    }
}

接下来,要在应用程序的特殊文件夹中创建临时文件,只需引用 App 类公开的文件夹路径即可:

App application = (App)Application.Current;
string tempFolderPath = application.ApplicationTempDirectoryFullName;
string fileName = "temporaryFile.tmp";
string fullFileName = Path.Combine(tempFolderPath, fileName);
await using FileStream temporaryFile = File.Create(fullFileName);
英文:

You can start your cleanup from the Application.Exit event handler.

You have basically two options to track or identify temporary files of your application:

  • Create all temporary files in a specific subfolder. This subfoldeer should be in the user's Windows temp folder to allow him to cleanup manually in case you missed something.
  • Create a global recent file list e.g. in your App class to store all temporary file paths

Then in the event handler either delete the temporary subfolder or delete each individual file by enumerating the global recent files list.

For external processes you don't have control where they create their temporary files. Those processes are responsible to cleanup their artifacts like you are responsible for your application artifacts.

For simplicity the following example uses static properties to make the temporary folder globally available.
In advanced scenarios for example when you are writing unit tests and use dependency injection, you should move the code that manages the temporary directory (create and delete) to a dedicated e.g. TempFolderManager class that you inject as shared instance into the application where needed.

public partial class App : Application
{
  public string? ApplicationTempDirectoryFullName => this.ApplicationTempDirectory?.FullName;

  // Make the actual DirectoryInfo private to protect against random manipulation
  private DirectoryInfo ApplicationTempDirectory { get; set; }
  private const string TempFolderNamePrefix = "MyApplicationTemp";

  public App()
  {
    this.Exit += App_Exit; 

    // Create the temp folder 
    // and expose it as static property to allow your application to reference it
    // for example to read or create temporary files.
    this.ApplicationTempDirectory = Directory.CreateTempSubdirectory(App.TempFolderNamePrefix);
  }

  void App_Exit(object sender, ExitEventArgs e)
  {
    // Delete the temporary folder and all sub-items (recursively)
    this.ApplicationTempDirectory.Delete(true);
  }
}

Next, to create a temporary file in the application's special folder simply reference the folder path exposed by the App class:

App application = (App)Application.Current;
string tempFolderPath = application.ApplicationTempDirectoryFullName;
string fileName = "temporaryFile.tmp";
string fullFileName = Path.Combine(tempFolderPath, fileName);
await using FileStream temporaryFile = File.Create(fullFileName);

答案3

得分: 0

Finally figured it out. The situation was reduced to the known MvvmCross problem https://github.com/MvvmCross/MvvmCross/issues/3481#issuecomment-1614698577 (which is only partially fixed).

Resume:

  • I have only one window in my applicatoin. So, when I close this window, the WPF hanles this as "the last window is closing". In this case WPF does not fire Unloaded event for the Window and all its internals (https://github.com/dotnet/wpf/issues/1442#issuecomment-518472218)
  • MvvmCross uses exactly the Unloaded events to manage the lifetime of the views. So, for the views that belong to such a last window will not receive any of the end-of-life notifications.
  • The fix which is already implemented in MvvmCross only handles the case when you use window presentation. It does not handle the content presentation placed in the main window.

The fix: I used platform-specific solution from https://stackoverflow.com/questions/16656523/awaiting-asynchronous-function-inside-formclosing-event to handle the last window close and to await async operations in my view model. The drawback of this solution is that I use some knowledge about the internal implementation of the default MvvmCross WPF presenter to get the reference to the VM from the Window_closing handler, which is obviously fragile and is not a best practice at all.

英文:

Finally figured it out. The situation was reduced to the known MvvmCross problem https://github.com/MvvmCross/MvvmCross/issues/3481#issuecomment-1614698577 (which is only partially fixed).

Resume:

  • I have only one window in my applicatoin. So, when I close this window, the WPF hanles this as "the last window is closing". In this case WPF does not fire Unloaded event for the Window and all its internals (https://github.com/dotnet/wpf/issues/1442#issuecomment-518472218)
  • MvvmCross uses exactly the Unloaded events to manage the lifetime of the views. So, for the views that belong to such a last window will not receive any of the end-of-life notifications.
  • The fix which is already implemented in MvvmCross only handles the case when you use window presentation. It does not handle the content presentation placed in the main window.

The fix: I used platform-specific solution from https://stackoverflow.com/questions/16656523/awaiting-asynchronous-function-inside-formclosing-event to handle the last window close and to await async operations in my view model. The drawback of this solution is that I use some knowledge about the internal implementation of the default MvvmCross WPF presenter to get the reference to the VM from the Window_closing handler, which is obviously fragile and is not a best practice at all.

huangapple
  • 本文由 发表于 2023年6月29日 02:20:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76575765.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定