【发布时间】:2020-01-17 15:49:04
【问题描述】:
我有一个 VB.NET WinForms 应用程序从存储在网络共享上的可执行文件运行。在那个应用程序中,我在ApplicationEvents (Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException) 中定义了UnhandledException 处理程序。在我的处理程序中,我有一个方法可以在提示用户确认退出应用程序之前将异常详细信息记录到文本文件中。
但是,此应用程序“随机”崩溃并完全退出,而没有创建任何日志或显示消息框。这种行为发生在应用程序的不同可执行点,我正在拼命寻找原因。我猜测问题可能是由于网络连接暂时中断或与 PostgreSQL 数据库通信的其他问题,但我无法确认来源,因为没有堆栈跟踪或消息在应用程序从用户屏幕上消失之前提供的详细信息。
这应该是“简单的”,但我不知所措,因为我尝试了几件事,包括将大量代码块包装在 Try...Catch 块中,并向我的错误处理程序添加额外的日志记录功能。我尝试重新排列我的UnhandledException 处理程序中的代码以避免新对象实例化的任何问题(对于我的ErrorHandler 对象)。如果网络不可用,我在错误处理中添加了一个检查以在本地记录错误。如果关闭不是由用户直接发起的,我什至在主窗体的FormClosing 事件中添加了一个简单的消息框,以尝试至少让应用程序在关闭之前执行某事完全。
到目前为止,无论我尝试过什么,应用程序仍然会在看似随机的时间强制退出。用户将按下一个按钮来执行通常正常工作的许多方法中的任何一种。如果用户在被踢出后重新启动应用程序并再次执行完全相同的操作,则它可以正常工作。我需要完成的是某种形式的“白痴证明”错误处理,以便捕获并记录导致应用程序退出的任何内容。我确定目前我还没有想到一些事情,所以如果需要进一步澄清,请告诉我。
代码
应用程序的Startup 事件处理程序:
Private Sub MyApplication_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
Try
Common.ApplicationStartup(ApplicationSettings.CurrentUser)
Catch ex As Exception
Dim StartupException As New ErrorHandler(ex)
StartupException.LogException()
MessageBox.Show("You do not have permission to access this resource." & vbCrLf & vbCrLf &
"The application will now exit.")
System.Environment.Exit(1)
End Try
' *********************************************************************
' ** Notify the user if the application is running in test mode. **
' *********************************************************************
If ApplicationSettings.TestMode Then
MessageBox.Show("This application is currently running in Test Mode, and will use " &
"local paths for data and configuration information." & vbCrLf & vbCrLf &
"If you are trying to use this application with live data and see " &
"this message, please contact the IT HelpDesk for assistance.", "TEST MODE",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
If ApplicationSettings.CurrentUser.Department = Users.Employee.Department.IS Then
If MessageBox.Show("Do you want to continue in Test Mode?", "TEST MODE", MessageBoxButtons.YesNo,
MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = DialogResult.No Then
ApplicationSettings.TestMode = False
End If
End If
End If
' *********************************************************************
' ** Initialize any application-specific settings here. **
' *********************************************************************
Try
'If ApplicationSettings.TestMode AndAlso ApplicationSettings.CurrentUser.Department = Users.Employee.Department.IS Then
' MessageBox.Show("If you have any additional parameters/settings to configure for this application, " &
' "please do so before commenting out this message.",
' "DEVELOPMENT WARNING", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
'End If
Catch ex As Exception
Dim ExHandling As New Common.ErrorHandler(ex)
ExHandling.LogException()
MessageBox.Show("There was a problem with initializing the application's configuration." & vbCrLf & vbCrLf &
"The application will now exit.")
System.Environment.Exit(2)
End Try
End Sub
ApplicationStartup 方法:
Public Sub ApplicationStartup(ByRef CurrentUser As Users.Employee)
' *********************************************************************
' ** Default the TestMode variable to False. If the check for **
' ** whether or not the application is running from the IDE fails, **
' ** the application should assume that it's running live. **
' *********************************************************************
ApplicationSettings.TestMode = False
' *********************************************************************
' ** Perform a check of whether or not the application is running **
' ** from the IDE or the Debug folder. **
' *********************************************************************
SetTestMode()
' *********************************************************************
' ** Retrieve any parameters sent to the executable from the command **
' ** line and determine if the application is running from the task **
' ** scheduler. **
' *********************************************************************
ApplicationSettings.ScheduledTask = False
ApplicationSettings.RuntimeParameters = System.Environment.GetCommandLineArgs().ToList
If Not ApplicationSettings.RuntimeParameters Is Nothing AndAlso ApplicationSettings.RuntimeParameters.Count > 0 Then
For Each Parameter As String In ApplicationSettings.RuntimeParameters
If Parameter.ToUpper.Contains("SCHEDTASK") Then
ApplicationSettings.ScheduledTask = True
Exit For
End If
Next
End If
' *********************************************************************
' ** Set up the CurrentUser object by querying Active Directory and **
' ** the PostgreSQL database for details. **
' *********************************************************************
Try
If CurrentUser.ADUserName Is Nothing OrElse String.IsNullOrEmpty(CurrentUser.ADUserName) Then
CurrentUser = New Users.Employee(Environment.UserName)
End If
Catch UserEx As Exception
Dim ExHandler As New ErrorHandler(UserEx)
ExHandler.LogException()
Throw UserEx
End Try
If CurrentUser Is Nothing Then
Throw New Exception("Username " & Environment.UserName & " was not found in Active Directory.")
ElseIf CurrentUser.Enabled = False Then
Throw New Exception("Username " & Environment.UserName & " is not a currently active employee.")
End If
' *********************************************************************
' ** Default the DBCommandTimeout variable to 30. **
' *********************************************************************
ApplicationSettings.DBCommandTimeout = 30
End Sub
Private Sub SetTestMode()
' *********************************************************************
' ** Use the Debug.Assert to call the InTestMode function, which **
' ** will set the TestMode variable to True. Debug.Assert will only **
' ** execute if the program is running from a debugging version of **
' ** the code (in Design-Time, or from the Debug folder). When the **
' ** code is running from a compiled executable, the Debug.Assert **
' ** statement will be ignored. **
' *********************************************************************
Debug.Assert(InTestMode)
End Sub
Private Function InTestMode() As Boolean
' *********************************************************************
' ** Set the global TestMode variable to True. This function is **
' ** only called in debug mode using the Debug.Assert method in the **
' ** SetTestMode Sub. It will not be called if the application is **
' ** running from a compiled executable. **
' *********************************************************************
Common.ApplicationSettings.TestMode = True
Return True
End Function
UnhandledException 事件处理程序:
Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException
Dim Response As DialogResult = DialogResult.Yes
Response = MessageBox.Show("An unknown error occurred in the application." & vbCrLf & vbCrLf &
"Do you want to exit the application?", "UNHANDLED EXCEPTION",
MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1)
Dim UnhandledError As New ErrorHandler(e.Exception)
UnhandledError.LogException()
If Response = DialogResult.Yes Then
e.ExitApplication = True
Else
e.ExitApplication = False
End If
End Sub
主窗体的FormClosing事件:
Private Sub frmMain_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If Not e.CloseReason = CloseReason.UserClosing Then
MessageBox.Show("The application has encountered some sort of problem and is closing.")
End If
End Sub
如果您想/需要查看更多代码,请告诉我。正如我所说,错误发生在应用程序执行中看似随机的点,并且在一次尝试或另一次尝试之间不一致。
2020 年 7 月 1 日更新
我有一段时间没有回到这个话题,因为我将可执行文件(和支持库)的副本移动到用户的本地驱动器,并让她从那里运行应用程序。当她使用该副本时,她并没有像上面描述的那样从程序中“启动”(她在这里和那里有一些错误,但这些都按预期由我的异常处理例程处理)。
几个月后,我有理由将用户切换回使用网络共享中的可执行文件副本。我刚刚收到用户的报告,她再次遇到被随机踢出应用程序而没有任何警告或错误的问题,而且我没有收到任何异常“报告”。对我来说幸运的是,她在记录事件方面做得很好。
奇怪的是,有时这些崩溃发生在她没有做任何“特别”的事情时。有几次,当她简单地单击其中一个工具条菜单以显示子菜单的下拉列表时,就会发生这种情况。我已经检查过,这些父工具条菜单没有任何事件处理代码,所以它不像正在执行任何查询或其他指令。它应该只是显示子菜单。
FWIW,几周前,我们的办公室和存储这些可执行文件的服务器之间存在严重的连接问题(托管虚拟机通过站点到站点 VPN 访问)。尽管我在其他任何地方都没有看到任何数据包丢失,但我在 VPN 上丢失了大约 10% 的数据包。我从来没有发现是什么导致了数据包丢失,但它似乎已经解决了,我只能假设这里和那里之间的一个 ISP 有一个他们修理/更换的故障设备。当我通过 VPN 对服务器运行 PING 测试时,我没有看到任何明显的数据包丢失(可能是数千个数据包中的 1 个数据包)和 15-35 毫秒的响应时间。
在这一点上,我只是猜测(显然),但我认为 VPN 连接上可能发生某种“超时”,导致与代码库的连接丢失。这是一个完整的 pS.W.A.G. ((伪)Scientific Wild-@$$ Guess),但我正在尝试提出一个可行的解决方案来解决这个问题。
我的想法
一个想法是这样的:我所有的内部应用程序都在这个服务器上运行,并且每个支持库都存储在可执行文件夹中。是的,这意味着我在服务器上的不同文件夹中存储了许多库的多个副本。我一直想要减少这种重复,但我真的没有/花时间找出最好的方法来做到这一点。此时,我正在考虑为每个工作站安装某种“安装程序”包,以将必要的库放入每个用户的 GAC(全局程序集缓存),而不是通过 VPN 访问它们。
唯一的问题(我能想到的)是有几个遗留系统使用相同库的不同版本。例如,我目前的开发是使用 Npgsql v4.1.3.1,但是有一些应用程序仍在使用 v2.x,我真的没有时间去检查每个应用程序来查找哪些应用程序正在/没有使用当前版本和实现版本升级。这只是会出现此类问题的众多库之一,因此我想我需要尝试将所有正在使用的版本安装到每个 GAC。
另一个想法: 将所有可执行文件带回本地服务器(而不是通过 VPN)并更改所有快捷方式以指向该版本,而不是需要 VPN 的版本。显然,这样做的好处是减少了对互联网连接和第三方系统等事物的依赖,并减少了延迟。
this 选项的问题在于,我的老板完全“不支持”它。当我过去提出类似建议时,他们的回应是“我们正在为托管服务器付费,他们应该支持它......”好吧,我们都知道类似这很可能超出了对第三方服务器主机的任何合理支持请求的范围。
我真的倾向于 GAC 选项 - 至少作为第一步 - 但在我开始走这条路之前,我需要做一些研究。还有其他人对我可以处理这个问题的方法有其他建议吗?我的想法真的快用完了,我必须找到一个真正可行且可持续的解决方案。
更多信息
我已经实现了来自@djv 的以下建议,将应用程序的启动包装在启动新线程的“启动”形式中,但仍然无法捕获导致崩溃的任何原因。该应用程序仍然只是周期性地死掉,到目前为止我完全没有找到任何日志记录。
我还在ApplicationEvents 中包含了一个非常简单的NetworkAvailabilityChanged 事件处理程序,以尝试捕捉那里发生的事情。
Private Sub MyApplication_NetworkAvailabilityChanged(sender As Object, e As NetworkAvailableEventArgs) Handles Me.NetworkAvailabilityChanged
If Not My.Computer.Network.IsAvailable Then
MessageBox.Show("Network connection has been lost.", "NETWORK CONNECTION TESTING", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
End Sub
不幸的是,即使 也没有给我任何额外的见解,因为用户从未见过 MessageBox。
我已经在用户的 Windows 事件日志中发现了一个错误,该错误似乎与最近的事件相对应,但我不确定它的确切含义:
EVENT ID 1000
---
Faulting application name: <EXECUTABLE_NAME>.exe, version: 1.0.0.0, time stamp: 0x9d491d36
Faulting module name: clr.dll, version: 4.8.4180.0, time stamp: 0x5e7d1ed7
Exception code: 0xc0000006
Fault offset: 0x000cc756
Faulting process id: 0xe570
Faulting application start time: 0x01d64fc245d7d922
Faulting application path: \\<SERVER_NAME>\<SHARE_PATH>\<EXECUTABLE_NAME>.exe
Faulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Report Id: 7016e3cc-7406-4854-95be-dbe3231447e7
Faulting package full name:
Faulting package-relative application ID:
这似乎表明 CLR 中的某些东西正在崩溃,但这似乎并没有给我提供比以前更多的信息。
针对特定问题的进一步诊断/研究
在事件日志中进一步挖掘之后,我发现了上述事件发生前后的几个错误:
EVENT ID 1005
---
Windows cannot access the file for one of the following reasons: there is a problem with the network connection, the disk that the file is stored on, or the storage drivers installed on this computer; or the disk is missing. Windows closed the program <EXECUTABLE_NAME> because of this error.
Program: <EXECUTABLE_NAME>
File:
The error value is listed in the Additional Data section.
User Action
1. Open the file again. This situation might be a temporary problem that corrects itself when the program runs again.
2. If the file still cannot be accessed and
- It is on the network, your network administrator should verify that there is not a problem with the network and that the server can be contacted.
- It is on a removable disk, for example, a floppy disk or CD-ROM, verify that the disk is fully inserted into the computer.
3. Check and repair the file system by running CHKDSK. To run CHKDSK, click Start, click Run, type CMD, and then click OK. At the command prompt, type CHKDSK /F, and then press ENTER.
4. If the problem persists, restore the file from a backup copy.
5. Determine whether other files on the same disk can be opened. If not, the disk might be damaged. If it is a hard disk, contact your administrator or computer hardware vendor for further assistance.
Additional Data
Error value: C00000C4
Disk type: 0
还有这个:
EVENT ID 1026
---
Application: <EXECUTABLE_NAME>.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Runtime.InteropServices.SEHException
at <ROOT_NAMESPACE>.frmPayments.get_instance()
at <ROOT_NAMESPACE>.frmMain.tsmiProcessPayments_Click(System.Object, System.EventArgs)
at System.Windows.Forms.ToolStripItem.RaiseEvent(System.Object, System.EventArgs)
at System.Windows.Forms.ToolStripMenuItem.OnClick(System.EventArgs)
at System.Windows.Forms.ToolStripItem.HandleClick(System.EventArgs)
at System.Windows.Forms.ToolStripItem.HandleMouseUp(System.Windows.Forms.MouseEventArgs)
at System.Windows.Forms.ToolStripItem.FireEventInteractive(System.EventArgs, System.Windows.Forms.ToolStripItemEventType)
at System.Windows.Forms.ToolStripItem.FireEvent(System.EventArgs, System.Windows.Forms.ToolStripItemEventType)
at System.Windows.Forms.ToolStrip.OnMouseUp(System.Windows.Forms.MouseEventArgs)
at System.Windows.Forms.ToolStripDropDown.OnMouseUp(System.Windows.Forms.MouseEventArgs)
at System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.ToolStrip.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.ToolStripDropDown.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
at System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
at System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
at System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
at System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
at <ROOT_NAMESPACE>.StartupForm.Main()
at System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
at System.Threading.ThreadHelper.ThreadStart()
对错误代码和异常信息做进一步的研究,看起来问题实际上是由于通过 VPN 从网络共享加载的可执行文件。
我找到的一些资料:
- Unable to start applications from network “0xc0000006”
- Performance penalties for .NET app running from shared network folder
- How does IMAGE_FILE_NET_RUN_FROM_SWAP in an EXE file affect runtime libraries
最后一个让我对 PE 文件选项以及如何为 VB.NET 应用程序设置它们进行了一些研究,但我没有找到足够的东西来感觉追求这种思路会带来足够的好处。
我想这意味着我需要做一些事情来将所有内容通过 VPN 带回执行。也许我会为应用程序进行某种实际的本地安装(现在我需要弄清楚如何实际做到这一点,但这远远超出了这个问题的范围)。我对此并不特别高兴,但至少我知道从这里往哪个方向发展。
回到最初的问题
但是,这个仍然并没有回答我最初关于如何捕获和处理这些应用程序杀手异常的问题。如果应用程序崩溃至少给了我一些关于当时发生的事情的迹象,我会“很好”。我认为ApplicationEvents 中的UnhandledException 处理程序甚至可以捕获这些,但对SEHException 的进一步研究至少可以帮助我了解为什么它没有 - 请参阅SEHException not caught by Try/Catch。我知道尝试定义规则来处理或忽略可能在UnhandledException 事件处理程序中出现的每一种异常类型将是一场噩梦,但它会更好,对故障排除更有帮助至少看到东西。
【问题讨论】:
-
了解更多有关您的应用程序的信息会很有帮助。 VB? C#?控制台应用程序?表格? WPF?
-
另外,请考虑查看 Windows 事件日志以查看那里是否报告了错误。
-
有几种方法可以做到这一点。你如何开始你的申请?你能显示一些代码吗?
-
@G_Hosa_Phat 你试过从本地机器而不是网络共享运行它吗?
-
@G_Hosa_Phat windows 是否提供应用程序崩溃的对话框?如果是这样,您可以在单击该对话框上的“确定”之前生成转储吗?如果是,那可以用来调查崩溃发生的原因。您还可以使用注册表设置为崩溃创建转储文件:stackoverflow.com/questions/30121822/…
标签: vb.net winforms error-handling crash