This is the first in a series of articles relating to the management of external processes from within .NET.  In my first article I will start with the basics: using the Process class to launch external processes and play with Priority and Process Affinity.

 

Safely Launching Processes

It has always been very simple to launch a fresh process in .NET:

Process notepadProc = System.Diagnostics.Process.Start;

Now as far as best practices go, Process.Start will throw an exception if it for any reason can’t open the file you specify.  Because of this, you should always wrap your call to Start() in a try-catch block.  In most cases a Win32Exception will be thrown and in order to tell what really happened you will need to inspect it’s NativeErrorCode property:

  1: const int ERROR_FILE_NOT_FOUND = 2;
  2: 
  3: string processName = "notepad.exe";
  4: Process notepadProc = null;
  5: try
  6: {
  7:     notepadProc = System.Diagnostics.Process.Start(processName);
  8: }
  9: catch (Win32Exception ex)
 10: {
 11:     if (ex.NativeErrorCode == ERROR_FILE_NOT_FOUND)
 12:     {
 13:         Console.WriteLine("Error! " + processName + " Not Found!");
 14:     }
 15:     else
 16:     {
 17:         Console.WriteLine("System level error has occured: " + ex.NativeErrorCode);
 18:     }
 19:     return;
 20: }

For more information on Win32Exception and a list of all possible NativeErrorCodes see the this very daunting list.

 

Changing Priority

At this point you now have a launched instance of notepad and have a handle to it’s process.  Using this handle you can do almost all of the same things available to you in the Windows Task Manager.  In particular it’s very easy to change a process’s priority:

notepadProc.PriorityClass = ProcessPriorityClass.AboveNormal; 

It is also very easy to toggle if a Process’s priority is boosted when it has desktop focus:

notepadProc.PriorityBoostEnabled = false; 

 

Changing Affinity

It can also be very useful to change the CPU affinity of a process.  This allows you to determine which Virtual CPUs or Cores a process is allowed to run on.  To do this you need to create a bit mask where each bit represents one of the processors on your system.  By toggling each bit you are controlling the ability of the process or use that Core.  For example, to only allow access to the first Core on the system:

notepadProc.ProcessorAffinity = new IntPtr(0x001); 

Allowing access to the first two cores would require setting the first two bits of the IntPtr:

notepadProc.ProcessorAffinity = new IntPtr(0x003); 

Setting CPU affinity is useful in many cases.  First of all, many older pieces of software have problems on new, multi-core CPUs.  Secondly, you may not want to allow a mutli-threaded process to bog down all of your cores.  For more information on CPU Affinity see Edmond Woychowsky’s blog on the topic as well as Microsoft’s MSDN documentation on the property.

 

Doing the Same For Each Individual Thread

For a deeper level of control you can reach into a process via the Process object’s Thread property and gain direct access to a ProcessThread object for each of it’s threads.  In this way, you can externally micromanage the priority and affinity of each of the threads.

foreach (ProcessThread thread in notepadProc.Threads)
{
    thread.PriorityLevel = ThreadPriorityLevel.AboveNormal;
    thread.PriorityBoostEnabled = true;
    IntPtr threadAffintiyMask = new IntPtr(0x003);
    thread.ProcessorAffinity = threadAffintiyMask;
}

 

Full Source

Now to bring it all together, here is a little program that will first fiddle with process priorities and thread priorities and then finally loop to display memory and thread usage information while the program is executing:

  1: using System;
  2: using System.Diagnostics;
  3: using System.ComponentModel;
  4: using System.Threading;
  5: 
  6: namespace ProcessFiddler
  7: {
  8:     class Program
  9:     {
 10:         const int ERROR_FILE_NOT_FOUND = 2;
 11: 
 12:         static void Main(string[] args)
 13:         {
 14:             string processName = "notepad.exe";
 15:             Process notepadProc = null;
 16:             try
 17:             {
 18:                 notepadProc = System.Diagnostics.Process.Start(processName);
 19:             }
 20:             catch (Win32Exception ex)
 21:             {
 22:                 if (ex.NativeErrorCode == ERROR_FILE_NOT_FOUND)
 23:                 {
 24:                     Console.WriteLine("Error! " + processName + " Not Found!");
 25:                 }
 26:                 else
 27:                 {
 28:                     Console.WriteLine("System level error has occured: " + ex.NativeErrorCode);
 29:                 }
 30:                 return;
 31:             }
 32: 
 33:             notepadProc.PriorityClass = ProcessPriorityClass.AboveNormal;
 34:             notepadProc.PriorityBoostEnabled = true;
 35: 
 36:             IntPtr affintiyMask = new IntPtr(0x003);
 37:             notepadProc.ProcessorAffinity = affintiyMask;
 38: 
 39:             foreach (ProcessThread thread in notepadProc.Threads)
 40:             {
 41:                 thread.PriorityLevel = ThreadPriorityLevel.AboveNormal;
 42:                 thread.PriorityBoostEnabled = true;
 43:                 IntPtr threadAffintiyMask = new IntPtr(0x003);
 44:                 thread.ProcessorAffinity = threadAffintiyMask;
 45:             }
 46: 
 47:             while (!notepadProc.HasExited)
 48:             {
 49:                 System.Console.WriteLine(processName + " currently has " + notepadProc.Threads.Count + " threads.");
 50:                 System.Console.WriteLine("Overall Memory Usage: " + notepadProc.WorkingSet64.ToString());
 51:                 foreach (ProcessThread thread in notepadProc.Threads)
 52:                 {
 53:                     System.Console.WriteLine( "Thread " + thread.Id + " Total Processor Time: " + thread.TotalProcessorTime ); 
 54:                 }
 55:                 Thread.Sleep(1000);
 56:             }
 57: 
 58:             System.Console.WriteLine("Process exited. Hit enter to continue.");
 59:             System.Console.Read();
 60: 
 61:         }
 62: 
 63:     }
 64: }
 65: 

 

Conclusion

I have only just scratched the surface here on what is possible with the Process class. Almost all of the information and functionality in the Task Manager is available for quick and easy access.  I encourage you to explore the MSDN documentation further.  It’s also possible to interact with and control processes directly.  As for that, I will be going through it in future posts.