当使用使用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;
}
// ...
}
检查所写文件的代码是在主窗体的构造函数中加载的:
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();
});
}
所有这一切的逻辑都很好。应用程序正确检测始终写出文件的辅助实例,第一个实例选取文件并激活/加载命令行中指定的文件。
但是,辅助实例崩溃硬时出现无法解决的错误(AppDomain.UnhandledException
事件被挂钩但不会触发)会在桌面上弹出Windows错误对话框。
次要负载在启动时大约有80%的时间都会崩溃 - 它不一致,但频繁发生,而且频繁发生。
如果我删除File.WriteAllText()代码,则不会发生崩溃。如果我删除FileWatcher代码,则不会发生崩溃。如果两者都有效: Boom 。 IOW,文件写入和FileWatcher都需要发生以便崩溃 - 如果一个未激活,则不会发生崩溃。我尝试用try / catch包装File.WriteAllText()调用,但它没有被触发。代码退出我的用户函数后发生故障,我似乎无法控制错误。
其他奇怪之处:</ p>
我还尝试将App.Current.Shutdown()
代码更换为Environment.Exit()
这更好 - 崩溃的频率要低得多,但仍然大约10%的时间都会发生。
当所有辅助实例正在执行的操作是将文件写入磁盘时,可能导致应用程序硬崩溃的原因是什么?
更新
事实证明,崩溃问题根本与文件写入/ FileWatcher操作无关。我创建了相同代码的NamedPipe版本,但仍然看到了失败。
事实证明,真正的罪魁祸首是WPF用于在启动时将图像启动到屏幕的 SplashScreen 。虽然在WPF术语中不是一个完整的“窗口”,但是这个窗口是在一个新线程上启动的,并且在完全初始化之前关闭,在SplashScreen线程完成之前杀死应用程序,这会导致内核崩溃。解决方法是a)删除启动画面,b)手动管理启动画面,不要在早期退出时显示它或c)在退出前明确关闭启动画面:
SplashScreen.Close(TimeSpan.MinValue);
Environment.Exit(0);
我写了一篇博文,其中更详细地介绍了这个问题:
答案 0 :(得分: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();
}