英文:
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
视图实现IDisposable
和IAsyncDisposable
-Dispose & DisposeAsync
根本没有被调用。 - 在
TipViewModel
视图模型中重写ViewDestroy
- 也没有被调用。 - 在
TipView
本身内部订阅TipView.Unload
事件 - 事件处理程序也没有被调用。 - 检查要重载或实现到
MvxWpfSetup
和IMvxAppStart
中的相关方法 - 没有找到相关内容。 - 尝试在
Window
级别处理Window.Close
事件,然后将调用传递到View
(MvxWpfView
)或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
andIAsyncDisposable
for view modelTipViewModel
and viewTipView
.Dispose & DisposeAsync
just not called at all. - override
ViewDestroy
in theTipViewModel
view model - it is also not called. - subscribe to the
TipView.Unload
event inside theTipView
itself - event handler not called. - checked for relevant methods to overload or implement into the
MvxWpfSetup
andIMvxAppStart
- nothing relevant found. - tried to handle
Window.Close
event on theWindow
level then transfer the call into theView
(MvxWpfView
) orViewModel
, 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 theWindow
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 theWindow
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论