简体   繁体   中英

WPF App.OnStartup() Crashes with Writing Files and FileWatcher

I have a really crazy issue when working with a WPF application that's using the Singleton instance pattern to insure only a single instance is running. The single instance detection and command line forwarding mechanics work fine, however, as part of startup code that exits on secondary instances which writes out a file to disk which is picked up by the primary application via a FileWatcher . The secondary instance frequently crashes hard with a Kernel level error.

The startup code that checks for secondary instances and randomly crashes does this:

    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;
            }

        //  ...           
    }

The code that checks for the file that was written is loaded in the Main form's constructor:

           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;

And the handler then checks for the file like this:

    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();    
        });
    }

The logic for all this works fine. The application properly detects the secondary instance which always writes out the file, and the first instance picks up the file and activates/loads the files specified in the command line.

However, the secondary instance crashes hard with an untrappable error ( AppDomain.UnhandledException event is hooked but doesn't fire) that pops up a Windows error dialog onto the desktop.

The secondary loads crash about 80% of the time they are launched - it's inconsistent, but much more frequently than not.

If I remove the File.WriteAllText() code, no crash occurs. If I remove the FileWatcher code, no crash occurs. If both are active: Boom . IOW, both the File writing and FileWatcher need to happen in order to crash - if one is not active, no crash happens. I've tried wrapping the File.WriteAllText() call with try/catch, but it doesn't get triggered. The failure occurs after the code exits my user function and I seem to have no control over the error.

Other oddities:

  • The failure never occurs under Debug
  • I can't attach a debugger from the Windows crash
  • MessageBox.Show() in OnStartup() just flashes the MB (not modal)

I also tried replacing the App.Current.Shutdown() code with Environment.Exit() which is better - crashes are much less frequent but they still happen about 10% of the time.

What could be causing this hard crash of the application, when all the secondary instance is doing is writing out a file to disk?

UPDATE
So it turns out that the crashing problem isn't related to the File writing/FileWatcher operation at all. I created a NamedPipe version of the same code and still saw failures.

It turns out the real culprit was the SplashScreen that WPF uses to launch an image to the screen at startup. Although not a full 'window' in WPF terms this window is launched on a new thread and shutting down early before full initialization kills the application before the SplashScreen thread can complete which causes the Kernel crash. The workaround is to a) remove the splash screen, b) manually manage the splash screen and don't show it for early exit or c) close the splash screen explicitly before exiting:

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

I've written up a blog post that in part covers this issue in more detail:

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

try using FileStream instead, then you can ensure the closure of the file handle with 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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM