views:

175

answers:

5

I'm writing a c# program that will launch many child processes. At some time later on, I'll need to retrieve those processes by ID and then match those processes to a set of processes stored in a Dictionary that were added to the Dictionary when they were first created. However, I'm running into a problem that seems like pure ridiculousness...

Process notepad = new Process();
notepad.StartInfo.FileName = "notepad";
notepad.Start();

Process n2 = Process.GetProcessById(notepad.Id);

Debug.WriteLine(notepad == n2);       //'False', but Why isn't this true???
Debug.WriteLine(notepad.Id == n2.Id); //'True'

I've used .NET Reflector to find out that GetProcessById returns a 'new Process(...)', but it seems like it should just find a reference to the already running process and return it instead.

You can assume the first Debug statement is essentially a call like

MyCustomDataType data = myDictionary[notepad];

I would expect to get the data I originally inserted, instead I get a KeyNotFoundException probably because the default comparer is doing a reference check. To counter this, I've added a custom IComparer on my dictionary that just checks that the two Process objects have the same ID, so I can get the associated data as expected. However, this has its own problem in that Processes that are not running do not have process IDs, so sometimes the call in my custom IComparer to Process.ID throws an InvalidOperationException!!! So, I've fixed one problem only to create another.

So, I guess I have two questions:

  • Why doesn't .NET just return a reference to an already running Process instance?
  • What can I do to match processes stored in my dictionary since using process ID is not always valid for the lifetime of the Process object?
+2  A: 

What would you want .net to do, in this case?

1st Executable

Process notepad = new Process();
notepad.StartInfo.FileName = "notepad";
notepad.Start();

Assume that you know the processId of the notepad instance.
In the 2nd executable, you would want to get hold of that process using the Id

2nd Executable

Process n2 = Process.GetProcessById(notepadIdInputByUser);

Object reference comparison can be done within an application, provided the object construction is in your hand (as against the operating system).

How will .net get you the instance of Process from the 1st Executable into the 2nd Exectuable?

EDIT: ALthough Process is a class, it cannot be reference compared.
It acts like a struct, where you could do member comparison.

shahkalpesh
+2  A: 

1) Your first question is analogous to:

var a = new List<string>{"one", "two", "three"};
var b = new List<string>{"one", "two", "three"};

if(a == b) { Debug.WriteLine("awesome"); } // not going to happen

Nothing is keeping track of created List<string>'s which is the same for created Process's

2) I suggest you don't store Process in the dictionary and instead create another class that stores the process id and the Process references and does something intelligent when the process that the Process refers to isn't running anymore.

Qberticus
+2  A: 

Running programs are operating system processes, not instances of the Process managed type. The Process type is one type of managed wrapper object around an operating system concept. It allows control of the target process by hiding the P/Invoke calls. The operating system does not require a particular instance of the Process class perform these operations, which is why GetProcessById can return a new instance and still expect everything to work.

The ID is valid for the life of the process itself. Perhaps you could set EnableRaisingEvents to true and add an event handler to the Process.Exited event that removes the process from your cache?

280Z28
+5  A: 

Not really sure why you had to resort to Reflector, since the GetProcessByID MSDN documentation clearly states:

Creates a new Process component, and associates it with the existing process resource that you specify.

The framework can't return to you the same instance you have. To be able to do this, the framework would have to keep a reference to all Process instances ever created and compare the process ID for them to find out the one you already have. I'll leave to you to imagine the implications this would have on the memory and the perf of the framework.

Besides, the Process class is a wrapper around the process handle returned by the OpenProcess Win32 API. And it is possible to have multiple handles to the same process. And input/output redirection can be done on a per-handle basis, thus, it is expected scenario to be able to have two Process instances that represent the same process and one of them dealing with the output and error streams and the other one with the input stream.

On a separate note, it seems to me that you are using the Process object itself as the key for the Dictionary. But the Process object does not represent the identity of the actual process, it is just a representation of the process. You should use as a key something that represents the identity of the process instead.

Franci Penov
Hi Franci. Thanks so much for your clear explanation.What would you recommend to 'use as a key something that represents the identity of the process instead'? I can't really think of any in-built property besides the ID. Plus, I need the property to live for the life of the Process object regardless of if the Process is running or not.
Chad
the process ID is the representation of the identity f the process. :-) so, your key should be the process ID. of course, you obviously want to keep the Process object associated with it plus some additional data that you keep in your current dictionary. You have to options - either expand the data you keep in the dictionary with the Process object and key it by the ID, or add second dictionary where you keep the Process objects keyed by ID.
Franci Penov
Process IDs (PIDs) are not a good representation of the identity of the process. If a process exits, the OS is free to reuse the same PID for a new process later. A Tuple of the process ID and the time the process started (to deal with the pid recycling case) is a much better identity.
Matt Ellis
@Matt - yes, you are correct, the OS can reuse the PID. I should've mentioned that the PID is representation of the identity of the process for as long as the process is alive. To represent the identity of a process after t's dead you need the PID and the time at which the process was alive.
Franci Penov
@Matt or @Franci - can either of you describe how knowing when the process starts helps match (Equals) a Process object?
Chad
if your app runs long enough, it is possible taht two of your Process instances to have the same PID. This is not a problem for the framework, since internally they still differ by the handle. However, for your code this will pose an interesting situation. So, to alleviate this, you can also use the time the process was started as an additional piece of info. Thus, the code that compares the Process instance you get when enumerating should check the Process Id and StartTime property.
Franci Penov
Since these properties will not work if the process has already exited, you need to create a small wrapper class where you can store the values of these properties, as well as a reference to the Process class itself when you launch the process. And in the dictionary you use this wrapper as a key instead of the Process instance.
Franci Penov
Is it possible to also address the other issue at hand - that before or after I've started a process I want to add it to the dictionary, but the ID (and StartTime) properties throw exceptions before the process is started.
Chad
You can't get the ID or start time before you start the process. But after you start it you can copy the values and store them in your struct. This way even after the process has exited, you still have the associated values even though the properties don't work.
Franci Penov
A: 

Use ProcessId as key of your dictionary.

Don't be afraid to create a new instance of Process using GetProcessById every time you need it. It's a lightweight object and garbage collector will clean up for you.

Konstantin Spirin