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

Ric*_*ahl 6 c# windows wpf

在处理使用Singleton实例模式的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;
            }

        //  ...           
    }
Run Code Online (Sandbox Code Playgroud)

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

           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;
Run Code Online (Sandbox Code Playgroud)

然后处理程序检查文件,如下所示:

    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();    
        });
    }
Run Code Online (Sandbox Code Playgroud)

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

然而,二次实例崩溃有untrappable错误(AppDomain.UnhandledException事件挂钩,但不闪光),一个Windows错误对话框弹出到桌面上.

二级负载在启动时大约有80%的时间会崩溃 - 它不一致,但更频繁.

如果我删除File.WriteAllText()代码,不会发生崩溃.如果我删除FileWatcher代码,则不会发生崩溃.如果两者都活跃:热潮.IOW,文件写入和FileWatcher都需要发生以便崩溃 - 如果一个未激活,则不会发生崩溃.我尝试用try/catch包装File.WriteAllText()调用,但它没有被触发.代码退出我的用户函数后发生故障,我似乎无法控制错误.

其他奇怪之处:

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

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

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

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

事实证明,真正的罪魁祸首是WPF用于在启动时将图像启动到屏幕的SplashScreen.虽然在WPF术语中不是一个完整的"窗口",但是这个窗口是在一个新线程上启动的,并且在完全初始化之前关闭,在SplashScreen线程完成之前杀死应用程序,这会导致内核崩溃.解决方法是a)删除启动屏幕,b)手动管理启动屏幕,不要在提前退出时显示它或c)在退出之前显式关闭启动屏幕:

SplashScreen.Close(TimeSpan.MinValue);
Environment.Exit(0);
Run Code Online (Sandbox Code Playgroud)

我写了一篇博文,其中更详细地介绍了这个问题:

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

Dea*_*alk 0

尝试使用 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();
}
Run Code Online (Sandbox Code Playgroud)