WPF App.OnStartup() 在写入文件和 FileWatcher 时崩溃

Posted

技术标签:

【中文标题】WPF App.OnStartup() 在写入文件和 FileWatcher 时崩溃【英文标题】:WPF App.OnStartup() Crashes with Writing Files and FileWatcher 【发布时间】:2016-09-07 20:51:35 【问题描述】:

在使用 WPF 应用程序时,我遇到了一个非常疯狂的问题,该应用程序使用单例实例模式来确保只有一个实例正在运行。然而,单实例检测和命令行转发机制工作正常,作为在辅助实例上退出的启动代码的一部分,该辅助实例将文件写入磁盘,该文件由主应用程序通过FileWatcher 拾取。辅助实例经常严重崩溃并出现内核级错误。

检查辅助实例并随机崩溃的启动代码执行以下操作:

    protected override void OnStartup(StartupEventArgs e)
    
            bool isOnlyInstance = false;
            Mutex = new Mutex(true, @"MarkdownMonster", out isOnlyInstance);
            if (!isOnlyInstance)
            
                filesToOpen = " ";
                var args = Environment.GetCommandLineArgs();
                if (args != null && args.Length > 1)
                
                    StringBuilder sb = new StringBuilder();
                    for (int i = 1; i < args.Length; i++)
                    
                        sb.AppendLine(args[i]);
                     
                    filesToOpen = sb.ToString();
                

                File.WriteAllText(mmApp.Configuration.FileWatcherOpenFilePath, filesToOpen);

                Mutex.Dispose();

                // This blows up when writing files and file watcher watching
                // No idea why - Environment.Exit() works with no issue
                ShutdownMode = ShutdownMode.OnMainWindowClose;
                App.Current.Shutdown();

                return;
            

        //  ...           
    

检查写入文件的代码被加载到主窗体的构造函数中:

           openFileWatcher = new FileSystemWatcher(
                Path.GetDirectoryName(mmApp.Configuration.FileWatcherOpenFilePath),
                Path.GetFileName(mmApp.Configuration.FileWatcherOpenFilePath))
            
                NotifyFilter = NotifyFilters.LastWrite,
                EnableRaisingEvents = true
            ;
            openFileWatcher.Changed += openFileWatcher_Changed;
            openFileWatcher.Created += openFileWatcher_Changed;

然后处理程序会像这样检查文件:

    private void openFileWatcher_Changed(object sender, FileSystemEventArgs e)
    
        string filesToOpen = null;

        // due to write timing we may have to try a few times
        for (int i = 0; i < 100; i++)
        
            try
            
                if (File.Exists(mmApp.Configuration.FileWatcherOpenFilePath))
                
                    filesToOpen = File.ReadAllText(mmApp.Configuration.FileWatcherOpenFilePath);
                    File.Delete(mmApp.Configuration.FileWatcherOpenFilePath);
                    filesToOpen = filesToOpen.TrimEnd();
                                   
                break;
            
            catch
            
                Thread.Sleep(10);
            
        

        Dispatcher.Invoke(() =>
        

            if (!string.IsNullOrEmpty(filesToOpen))
            
                foreach (var file in StringUtils.GetLines(filesToOpen))
                
                    MessageBox.Show(file);
                    this.OpenTab(file.Trim());
                
            

            if (WindowState == WindowState.Minimized)
                WindowState = WindowState.Normal;

            this.Activate();    
        );
    

所有这一切的逻辑都很好。应用程序正确检测到总是写出文件的辅助实例,第一个实例拾取文件并激活/加载命令行中指定的文件。

但是,辅助实例崩溃hard 并出现无法捕获的错误(AppDomain.UnhandledException 事件已被钩住但不会触发),会在桌面上弹出一个 Windows 错误对话框。

辅助负载在启动时大约有 80% 的时间会崩溃 - 不一致,但频率要高得多。

如果我删除 File.WriteAllText() 代码,则不会发生崩溃。如果我删除 FileWatcher 代码,则不会发生崩溃。如果两者都处于活动状态:Boom。 IOW,文件写入和 FileWatcher 都需要发生才能崩溃 - 如果一个不活动,则不会发生崩溃。我尝试用 try/catch 包装 File.WriteAllText() 调用,但它没有被触发。代码退出我的用户功能后发生故障,我似乎无法控制错误。

其他怪事:

Debug 下永远不会出现故障 我无法从 Windows 崩溃中附加调试器 OnStartup() 中的 MessageBox.Show() 只是闪烁 MB(非模态)

我还尝试用 Environment.Exit() 替换 App.Current.Shutdown() 代码,这样会更好 - 崩溃的频率要低得多,但它们仍然发生大约 10% 的时间。

当所有辅助实例都在将文件写入磁盘时,可能导致应用程序发生这种硬崩溃的原因是什么?

更新 所以事实证明,崩溃问题根本与文件写入/FileWatcher 操作无关。我创建了相同代码的 NamedPipe 版本,但仍然看到失败。

事实证明,真正的罪魁祸首是 WPF 在启动时用来在屏幕上启动图像的 SplashScreen。尽管在 WPF 术语中不是一个完整的“窗口”,但此窗口在新线程上启动并在完全初始化之前关闭应用程序,在 SplashScreen 线程完成之前终止应用程序,从而导致内核崩溃。解决方法是 a) 删除启动画面,b) 手动管理启动画面并且不显示它以便提前退出​​或 c) 在退出前明确关闭启动画面:

SplashScreen.Close(TimeSpan.MinValue);
Environment.Exit(0);

我已经写了一篇博文,部分地更详细地介绍了这个问题:

http://weblog.west-wind.com/posts/2016/May/13/Creating-Single-Instance-WPF-Applications-that-open-multiple-Files

【问题讨论】:

等等,你说的“单例”只是指应用不能一次打开多次? 实现单例实例模式。然而,作为在辅助实例上退出的启动代码的一部分,单实例检测和机制工作正常。如果我理解不正确,我很抱歉,但你想要一个单身人士? 【参考方案1】:

尝试使用 FileStream 代替,然后您可以确保使用 FileStream.Flush 关闭文件句柄

using (FileStream fs = File.Create(mmApp.Configuration.FileWatcherOpenFilePath))

  byte[] info = new UTF8Encoding(true).GetBytes(filesToOpen);
  fs.Write(info, 0, info.Length);
  fs.Flush();

【讨论】:

这是我尝试的第一件事,但这没有区别 - 我看到较低级别的文件写入/关闭的行为相同。

以上是关于WPF App.OnStartup() 在写入文件和 FileWatcher 时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

WPF 是不是支持本机读取/写入 WMF/EMF 文件?

以编程方式向 WPF 中的文件添加写入权限

C#WPF。在 DatePickere 中写入时如何自动添加点?

C# WPF 生成图像访问被拒绝

WPF借助SaveFileDialog实现文件存储

Wpf 导出CSV文件