How do I correctly handle system exceptions?
I'm trouble with the exception handling. Specifically, I create a System.Diagnostic.Process object from the process identifier (PID), then I use it to execute my algorithm. I've noticed that this class throw InvalidOperation and ArgumentException exception when accessing to different properties, because the process has already exited while I'm accessing to the Process instance.
However, the algorithm uses other function which throw the same exceptions. The following code is the one which has raised the problem:
XmlWindow mWindow = new XmlWindow(new IntPtr(args.Message.GetContent<Int64>()));
Process mWindowProcess = mWindow.GetProcess();
XmlProcessInstance mWindowProcessInstance = null;
XmlProcessLayout pLayout = null;
Log(mWindow);
lock (mCoreData.ProcessList) {
try {
// Ensure matching XmlProcess
mCoreData.ProcessList.EnsureProcessManagement(mWindowProcess);
} catch (System.ComponentModel.Win32Exception eWin32Exception) {
sLog.WarnFormat("Unable to manage window creation ({0}, error code {1}).", eWin32Exception.Message, eWin32Exception.NativeErrorCode);
break;
}
}
lock (mCoreData.LayoutList) {
// Unmanaged process?
if (mCoreData.LayoutList.IsManagedProcessInstance(mWindowProcess) == false) {
lock (mCoreData.UnmanagedLayout) {
// Force process management
if ((mWindowProcessInstance = mCoreData.UnmanagedLayout.GetProcessInstance((uint)mWindowProcess.Id)) == null) {
mWindowProcessInstance = mCoreData.UnmanagedLayout.ManageProcessInstance((uint)mWindowProcess.Id, mCoreData.ProcessList);
sLog.DebugFormat("Layout \"{0}\" will manage explictly the process \"{1}\" ({2}).", mCoreData.UnmanagedLayout.Name, mWindowProcessInstance.ApplicationName, mWindowProcessInstance.InstanceId);
}
}
} else {
// Find the (managed) process instance
mWindowProcessInstance = mCoreData.LayoutList.GetProcessInstance((uint)mWindowProcess.Id);
}
}
Log(mWindowProcessInstance);
// Ensure window match
mWindowProcessInstance.ProcessAssociation.AssociatedItem.LearnWindowMatching(mWindow);
// Register process instance window
mWindowProcessInstance.LearnWindowTemplating(mWindow);
mWindowProcessInstance.Windows.Add(mWindow);
// Apply window template (if any)
mWindowProcessInstance.ApplyTemplateWindow(mWindow);
The problem is how to manage the InvalidOperationException exception. The code above doesn't work, since the exception could be thrown by SomeFunction开发者_如何学运维, instead by accessing the Process instance; I need to handle only those exception thrown by the mWindowProcess.
Of course I need a big one try/catch statement, because the usage of the variable mWindowProcess is very intensive
How this could be solved correctly?
You can use two try-catch blocks.
Process p = Process.GetProcessById(pid);
try {
SomeFunction(); // could throw InvalidOperationException
} catch (InvalidOperationException) { } catch { throw; }
try {
if (p.Id == 1234) { ... } // could throw InvalidOPerationException!
} catch (InvalidOperationException) { } catch { throw; }
Use two seperate try/catch blocks. Each block handles the same exception differently.
You could check Process.HasExited before each call and determine what to do if the process has exited at that point. It's unclear whether there is a systematic way to handle this for your application. Unfortunately you still need to check for the exception, since the process could terminate between the query call and the usage of the Process class. It's unfortunate that InvalidOperationException was used, since this is often used to indicate unrecoverable corrupted state.
The correct way to do this is, unfortunately, to put a try catch around every specific call for which you wish to handle the error. If you want to exit the larger usage block, you could then throw your own custom exception that's more indicative of the true failure (ProcessTerminatedException, for instance.) One option to clean this up:
public static int SafeGetId(this Process process)
{
if (process == null) throw new ArgumentNullException("process");
try
{
return process.Id;
}
catch (InvalidOperationException ex)
{
//Do special logic, such as wrap in a custom ProcessTerminatedException
throw;
}
}
Now you can call SafeGetId() everywhere you were previously accessing Id. You can create additional wrappers for other methods/properties that could fail.
I found a possible answer. Effectively this solution was unexpected as much as obvious...
This is quote of the Exception documentation:
Exception includes a number of properties that help identify the code location, the type, the help file, and the reason for the exception: StackTrace, InnerException, Message, HelpLink, HResult, Source, TargetSite, and Data.
The listed Exception properties really aid the exception catching. In my case, it is acceptable to catch only those exception thrown by the Process class. So, I suppose this code is the correct way to filter exceptions:
try {
....
} catch (InvalidOperationException eInvalidOperationException) {
if (eInvalidOperationException.TargetSite.DeclaringType == typeof(System.Diagnostics.Process)) {
// Exception when accessing mWindowProcess
} else
throw;
} catch (ArgumentException eArgumentException) {
if (eArgumentException.TargetSite.DeclaringType == typeof(System.Diagnostics.Process)) {
// Exception when accessing mWindowProcess
} else
throw;
}
This work for my code until the code access to only the one Process instance (mWindowProcess); in the case multiple Process variables (not directly related with mWindowProcess) throws those exceptions, they should be catched, and use the Exception.Data dictionary to notify the different situation.
The Exception class has a very effective control on the exception recognition.
精彩评论