tags:

views:

1199

answers:

3

I have some System.Diagnotics.Processes to run. I'd like to call the close method on them automatically. Apparently the "using" keyword does this for me.

Is this the way to use the using keyword?

foreach(string command in S) // command is something like "c:\a.exe"
{
    try
    {
        using(p = Process.Start(command))
        {
            // I literally put nothing in here.
        }
    }
    catch (Exception e)
    {
        // notify of process failure
    }
}

I'd like to start multiple processes to run concurrently.

A: 
try
{
   foreach(string command in S) // command is something like "c:\a.exe"
   {
      using(p = Process.Start(command))
      {
        // I literally put nothing in here.
      }

    } 
}
catch (Exception e)
{
    // notify of process failure
}

The reason it works is because when the exception happens, the variable p falls out of scope and thus it's Dispose method is called that closes the process is how that would go. Additionally, I would think you'd want to spin a thread off for each command rather than wait for an executable to finish before going on to the next one.

JB King
+15  A: 
using(p = Process.Start(command))

This will compile, as the Process class implements IDisposable, however you actually want to call the Close method.
Logic would have it that the Dispose method would call Close for you, and by digging into the CLR using reflector, we can see that it does in fact do this for us. So far so good.

Again using reflector, I looked at what the Close method does - it releases the underlying native win32 process handle, and clears some member variables. This (releasing external resources) is exactly what the IDisposable pattern is supposed to do.

However I'm not sure if this is what you want to achieve here.

Releasing the underlying handles simply says to windows 'I am no longer interested in tracking this other process'. At no point does it actually cause the other process to quit, or cause your process to wait.

If you want to force them quit, you'll need to use the p.Kill() method on the processes - however be advised it is never a good idea to kill processes as they can't clean up after themselves, and may leave behind corrupt files, and so on.

If you want to wait for them to quit on their own, you could use p.WaitForExit() - however this will only work if you're waiting for one process at a time. If you want to wait for them all concurrently, it gets tricky.

Normally you'd use WaitHandle.WaitAll for this, but as there's no way to get a WaitHandle object out of a System.Diagnostics.Process, you can't do this (seriously, wtf were microsoft thinking?).

You could spin up a thread for each process, and call `WaitForExit in those threads, but this is also the wrong way to do it.

You instead have to use p/invoke to access the native win32 WaitForMultipleObjects function.

Here's a sample (which I've tested, and actually works)

[System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
static extern uint WaitForMultipleObjects( uint nCount, IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds );

static void Main( string[] args )
{
 var procs = new Process[] {
  Process.Start( @"C:\Program Files\ruby\bin\ruby.exe", "-e 'sleep 2'" ),
  Process.Start( @"C:\Program Files\ruby\bin\ruby.exe", "-e 'sleep 3'" ),
  Process.Start( @"C:\Program Files\ruby\bin\ruby.exe", "-e 'sleep 4'" ) };
 // all started asynchronously in the background

 var handles = procs.Select( p => p.Handle ).ToArray();
 WaitForMultipleObjects( (uint)handles.Length, handles, true, uint.MaxValue ); // uint.maxvalue waits forever

}
Orion Edwards
+2  A: 

For reference: The using keyword for IDisposable objects:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

is just compiler syntax. What it compiles down to is:

Writer writer = null;
try
{
    writer = new Writer();
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

using is a bit better since the compiler prevents you from reassigning the writer reference inside the using block.

The framework guidelines Section 9.3.1 p. 256 state:

CONSIDER providing method Close(), in addition to the Dispose(), if close is standard terminology in the area.


In your code example, the outer try-catch is unnecessary (see above).

Using probably isn't doing what you want to here since Dispose() gets called as soon as p goes out of scope. This doesn't shut down the process (tested).

Processes are independent, so unless you call p.WaitForExit() they spin off and do their own thing completely independent of your program.

Counter-intuitively, for a Process, Close() only releases resources but leaves the program running. CloseMainWindow() can work for some processes, and Kill() will work to kill any process. Both CloseMainWindow() and Kill() can throw exceptions, so be careful if you're using them in a finally block.

To finish, here's some code that waits for processes to finish but doesn't kill off the processes when an exception occurs. I'm not saying it's better than Orion Edwards, just different.

List<System.Diagnostics.Process> processList = new List<System.Diagnostics.Process>();

try
{
    foreach (string command in Commands)
    {
        processList.Add(System.Diagnostics.Process.Start(command));
    }

    // loop until all spawned processes Exit normally.
    while (processList.Any())
    {
        System.Threading.Thread.Sleep(1000); // wait and see.
        List<System.Diagnostics.Process> finished = (from o in processList
                                                     where o.HasExited
                                                     select o).ToList();

        processList = processList.Except(finished).ToList();
        foreach (var p in finished)
        {
            // could inspect exit code and exit time.
            // note many properties are unavailable after process exits
            p.Close();
        }
    }
}
catch (Exception ex)
{
    // log the exception
    throw;
}
finally
{
    foreach (var p in processList)
    {
        if (p != null)
        {
            //if (!p.HasExited)
            // processes will still be running
            // but CloseMainWindow() or Kill() can throw exceptions
            p.Dispose();
        }

    }
}

I didn't bother Kill()'ing off the processes because the code starts get even uglier. Read the msdn documentation for more information.

Robert Paulson