10.3 进程
Framework API中的进程和OS进程一样。将不在此详细介绍这些内容,很多喜欢的Windows书籍应该有很好的介绍。有许多利用进程的有趣的事情,并且有许多在系统中运行的进程的统计信息。所有这些功能都可以通过使用System.Diagnostics命名空间的Process类得到。这些API允许使用正在退出的(已经运行的)进程或者创建并管理自己的进程。
10.3.1 退出进程
Process类有一系列的静态方法,它们将返回一个或多个实例以供使用。最直观的方法是静态GetCurrentProcess方法,它返回一个关于当前正在执行的进程的实例。GetProcessById用它的独特的OS进程标识符(Process Identifier,简称PID)获得一个进程,或者使用指定的PID没有发现活动的进程时,它就抛出一个ArgumentException异常。GetProcessByName方法返回一个按照给定名称执行的Process对象数组。最后,Process.GetProcesses返回一个所有当前活动的进程数组。
下面这段代码说明了这些方法的用法,具体方法是简单打印出一些系统中活动的进程的信息:
void ListProcesses()
{
Console.WriteLine("Current process:");
PrintProcess(Process.GetCurrentProcess());
Console.WriteLine("IE processes:");
Process[] iexplorerProcs = Process.GetProcessesByName("iexplore");
foreach (Process p in iexplorerProcs) PrintProcess(p);
Console.WriteLine("All active processes:");
Process[] allProcs = Process.GetProcesses();
foreach (Process p in allProcs) PrintProcess(p);
}
void PrintProcess(Process p)
{
Console.WriteLine(" -> {0} - {1}", p.ProcessName, p.PeakWorkingSet64);
}
也可以在另一台机器上访问进程。只要在这台机器上当前授权身份有管理性访问的权限。所有上面提到的方法都有一个采用额外的machineName string参数的重载。假设已经有权访问一台机器DevelopmentBox1并有权在其上做这些工作,下面这段代码将列出其上的所有活动进程:
Process[] allProcs = Process.GetProcesses("DevelopmentBox1");
foreach (Process p in allProcs) PrintProcess(p);
每个进程对象有一个和它相关的统计状态集。下面仅是其中一些比较有趣的属性:
public int BasePriority { get; }
public int ExitCode { get; }
public DateTime ExitTime { get; }
public bool HasExited { get; }
public int Id { get; }
public string MachineName { get; }
public IntPtr MaxWorkingSet { get; set; }
public IntPtr MinWorkingSet { get; set; }
public long NonpagedSystemMemorySize64 { get; }
public long PagedMemorySize64 { get; }
public long PagedSystemMemorySize64 { get; }
public long PeakPagedMemorySize64 { get; }
public long PeakVirtualMemorySize64 { get; }
public long PeakWorkingSet64 { get; }
public bool PriorityBoostEnabled { get; set; }
public ProcessPriorityClass PriorityClass { get; set; }
public long PrivateMemorySize64 { get; }
public TimeSpan PrivilegedProcessorTime { get; }
public string ProcessName { get; }
public IntPtr ProcessorAffinity { get; set; }
public bool Responding { get; }
public DateTime StartTime { get; }
public TimeSpan TotalProcessorTime { get; }
public TimeSpan UserProcessorTime { get; }
public long VirtualMemorySize64 { get; }
public long WorkingSet64 { get; }
任何这些方法获得的数据是及时的快照。为了得到最新的统计,必须在一个Process实例上执行void Refresh方法。对于远程进程,这会造成网络上的往返传输,因此应该小心使用。
进程交互
每个进程有3个主要的管道,可以用它们来和进程通信。它们是标准的输入、错误和输出。标准输入(也称为stdin)是对进程的键盘输入并且是Console.In.*方法族默认交互的管道。另一方面,标准输出和错误(也称为stdout和stderr)是进程用来与外部通信的管道。例如,对于控制台应用程序,使用这些管道在Console.Out.*和Console.Error.*操作期间向屏幕输出。更多的内容请参照第7章的I/O系统。
当一个进程执行时,将经常向它提供数据或者读取它输出的数据。Processs类明确地显示这些管道,这样就可以进行操作:StandardInput、StandardOutput和StandardError。 StandardInput是StreamWriter,而其他的是StreamReader。可以像使用任何其他基于Stream的类一样使用它们。
为了使输出可读,需要用输出重定向启动进程。具体做法是,构造一个ProcessStartInfo实例,将它的UseShellExecute属性设置为false,RedirectStandardOutput(或者Redirect- StandardError)属性设为true,然后将它传递给Processs构造函数:
// Construct our process start information:
ProcessStartInfo pi = new ProcessStartInfo("path_to_exe.exe");
pi.UseShellExecute = false;
pi.RedirectStandardOutput = true;
// Kick off the new process:
Process p = Process.Start(pi);
StreamReader sr = p.StandardOutput;
// And read some output from its stdout stream:
String line;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine("Read line: {0}", line);
}
p.WaitForExit();
通过使用BeginErrorReadLine和BeginOutputReadLine方法可以异步读取stdout和stderr流。这些操作都将异步发生,当数据可用时,对事件ErrorDataReceived和OutputDataReceived分别进行回调。这两种事件都是委托,提供包括一个string Data属性的事件参数,表示从进程读取的数据。还有Cancel *ReadLine方法,用于停止请求的异步读操作。
10.3.2 创建
Process类提供了一个静态Start工厂方法来创建一个新的进程。有多个重载,其中最简单的是只获得一个可执行文件的文件名。同时还有一个重载使用processStartInfo参数,允许指定多个有趣的设置来控制结果进程(例如,window类型、环境变量、输出重定向等)。Start创建一个新的进程来执行程序,启动它,然后会返回与新创建的进程相对应的Process实例。对于这些位于当前环境的PATH变量中的目录的程序而言,没有必要完全限定可执行的文件名。例如,下面简短的代码行将创建一个新的Internet Explorer实例:
Process p = Process.Start("iexplore.exe");
当可执行文件启动时,有时需要向其传递参数。为了做到这一点,可以使用获得基于string的arguments参数的重载。参数的值将被传递到可执行文件中,这正如从命令行或者快捷方式运行代码的情况一样。例如,下面这段代码打开了一个浏览特定网站的新的IE窗口:
Process p = Process.Start("iexplore.exe",
"http://www.bluebytesoftware.com/blog/");
传递参数的规则与命令行窗口中的一样。对于包含空格的参数,用双引号标记将其包围。
Process同样包含重载,该重载获得3个额外的参数:string username、SecureString- password和string domain。它们可以用来创建一个以不同账户运行的新的进程。例如,下面这段代码将产生一个以本地匿名账户运行的程序:
SecureString password = GetSecurePassword(); // Custom function
Process p = Process.Start("iexplore",
"http://www.bluebytesoftware.com/blog/", "Guest", password, "MyMachine");
注意,对于一个本地用户账户,传递本地机器的名称给域。如果要使用一个网络账户,将在这里传递适合的域值。同样注意必须使用SecureString实例表示密码。GetSecurePass word()方法不是Framework所提供的内容—— 一般需要创建这个方法来查找和生成SecureString实例。
10.3.3 终止
有多个方法监控甚至实际上是导致进程的终止。Process实例的HasExited属性将返回true,指明它已经退出。此外,ExitCode属性将获得进程的退出代码(这里自定义非0值,用来指明突然的和预料之外的终止),而ExitTime将返回一个DateTime,它表示在何时进程退出。
Process上的实例方法WaitForExit阻塞当前线程的执行直到目标进程已经退出。还有一个重载,它将在超时周期后超时,如果进程没有在这段周期中退出,则返回false。Exited事件也可以用来异步通知程序一个进程的退出。然而,为了使这个事件适当发生,必须将目标Process上的EnableRaisingEvents属性设置为true。
下面这段代码说明了一个事件处理程序的设置,该处理程序将在进程终止时被调用:
public void WatchProcess(Process p)
{
p.Exited += ExitHandler;
p.EnableRaisingEvents = true;
}
void ExitHandler(object sender, EventArgs e)
{
Process p = (Process)sender;
Console.WriteLine("{0} exited with code {1}", p.ProcessName, p.ExitCode);
}
Process类有一个实例Kill方法,它会突然终结目标进程。这和调用Win32的TerminateProcess函数是相同的。这强制使程序立刻退出,没有任何有次序的展开,并会造成系统范围的崩溃。进程退出代码将被设置为-1(非零值表示失败)。






