views:

86

answers:

4

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?

+1  A: 

Use two seperate try/catch blocks. Each block handles the same exception differently.

Brent Arias
+2  A: 

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; }
Zafer
What's the point of the extra `catch { throw; }`? AFAIK omitting that does not change the behavior of the code.
Wim Coenen
The point is not correcting the code. It's about showing that there can be two try-catch statements.
Zafer
+1  A: 

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.

Dan Bryant
A: 

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.

Luca
This would not work if the exception were thrown by any class besides Project, such as an internal helper class used in its implementation. It's a bad idea to rely on TargetSite for filtering, because the exact target site of an exception is an implementation detail that can easily change.
Dan Bryant
Right, so it "works" because Process implementation is correct. Is it reasonable to assume that? If so, it is a good try-catch-all pattern...
Luca