开发者

If 'Process.HasExited' throws an exception, can I assume that the process is gone?

I have the following code section, designed to count how many Excel processes are currently open:

Func<int> OpenExcelProcessesCount = 
    () => System.Diagnostics.Process.GetProcessesByName("Excel")
              .Where(p => !p.HasExited)
              .Count();

And then later I retrieve the count at various points, with code such as the following:

int excelAppCount = OpenExcelProce开发者_Go百科ssesCount();

This code has been running 100% fine for months. Then suddenly, today, it is consistently giving me an exception that reads the following:

Exception: ApplicationThreadException

Message: Access is denied

Stack Trace:

   at System.Diagnostics.ProcessManager.OpenProcess(Int32

processId, Int32 access, Boolean throwIfExited)

   at System.Diagnostics.Process.GetProcessHandle(Int32

access, Boolean throwIfExited)

   at System.Diagnostics.Process.get_HasExited()

   etc...

Basically, the call to Process.HasExited (which shows up as System.Diagnostics.Process.get_HasExited() in the stack trace, above) is failing. The error message "Access is denied" sounds like I don't have administrative privileges for the process, but the only Excel processes that exist would be created under my current user log-in, and the user can always access their own processes. My .NET code is also running under full trust.

The line that is ultimately failing is System.Diagnostics.ProcessManager.OpenProcess(Int32 processId, Int32 access, Boolean throwIfExited). I wonder if it is being passed in a value of 'true' for the 'throwIfExited' parameter. If this is the case, then I suppose that I could protect the call to Process.HasExited with a try-catch block and assume that if this fails that HasExited is, in fact, 'true'. But is this a safe assumption??

I am uneasy making a presumption like this, especially since the error message is "Access is denied." Does anyone have any ideas on how I might address this, or what I might test in an attempt to figure out what is going on?

The only similar thread I could find on Stack Overflow was the following: Why did hasExited throw ‘System.ComponentModel.Win32Exception’?. The answer given there was:

"Since you are doing runas, you only get SYNCHRONIZE access on the handle, not PROCESS_QUERY_INFORMATION access, hence GetExitCodeProcess fails, which results in hasEnded throwing a Win32 exception."

I don't really understand this answer and do not know if this applies in my case, but I thought I should mention it. If anyone feels that it is likely that this is the situation that I am facing, then if someone could try to clarify this answer for me, I would greatly appreciate it. (I'm an Excel programmer, I do not have much experience working with processes.)

Much thanks in advance...

Update:

Best I can tell, this is was a one-off corruption of some sort. The issues I faced started becoming increasingly bizarre as what had been a perfectly functioning set of unit tests was starting to have breakdowns at other "impossible" locations. A simple reboot corrected this issue and everything else I was facing.

My best guess is that I had some sort of bizarre corruption. Perhaps the ROT was corupted, and/or I had a hanging instance of Excel that was so corrupt that even 'Process' operations were not necessarily stable. Nothing conclusive, but this is all I can figure for now.

To the responders who took the time answer and help me out, I thank you.


Process.HasExited can throw an access denied exception if the target process is running elevated and your process isn't. The same happens with StartTime property, too.

I have written a blog post about this topic with a possible workaround: Bugs in System.Diagnostics.Process Class

The stack trace in the question also looks similar to the one that I have in the blog.


The answer you quote may apply in your case. As I understand it, it's basically saying that if the process you're looking at is running under a different user account, then HasExited can't get the permissions it needs to determine if the process has exited.

Now you say "the only Excel processes that exist would be created under my current user log-in." But suppose that's not the case. Suppose there's another Excel process running on the same box, under another user account? (Maybe one got started by somebody else's process using OLE Automation, and didn't get cleaned up properly or has hung.) Then GetProcessesByName would pick it up, but HasExited would fail.

So before calling your OpenExcelProcessesCount() method, add a bit of logging to dump the process IDs of all the processes returned from GetProcessesByName("Excel"). Then check these against Task Manager, or the number of Excel processes you expect to be running under your account. If there's an ID there that doesn't correspond to an "expected" Excel process, you could have a culprit.


8 years later and this problem still exists in the .NET Process class, forcing you to use unmanaged code to check on protected processes. Giorgio's answer and blog posts are very good, although testing whether a process has exited was slightly more complicated than he makes out :) Here's what worked for me:

    hProcess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION, false, processId);

    GetExitCodeProcess(hProcess, out uint ExitCode);
    if (ExitCode != 259) // ExitCode of 259 is STILL_ACTIVE
    {
        CloseHandle(hProcess);
        return false;
    }

    // do your thing, this process is alive and you have a handle now

    CloseHandle(hProcess)

And here are the declarations you need to stray into the land of unmanaged code:

    [DllImport("kernel32.dll")]
    static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr handle);

    [Flags]
    public enum ProcessAccessFlags : uint
    {
        PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
    }


After reading the documentation:

Use this method to create an array of new Process components and associate them with all the process resources that are running the same executable file on the local computer.

It looks to me like you don't need to check HasExited, since it only returns running processes.


return process.WaitForExit(0);

Seems to make less problems.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜