C#进阶技巧掌握外部进程的启动与控制(一):进程基础与基本操作

引言
在 C# 开发中,启动和控制外部进程是一项非常实用的技能。无论是执行系统命令、调用外部应用程序,还是构建复杂的自动化工具,掌握进程控制都能让你的应用程序功能更加强大。本系列文章将深入探讨 C# 中进程管理的各个方面,从基础概念到高级应用,帮助你全面掌握这一重要技能。
你是否曾经需要在 C# 应用中执行命令行工具?或者想要监控某个程序的运行状态?也许你正在构建一个需要与其他应用程序交互的系统?无论哪种情况,本系列文章都将为你提供清晰的指导和实用的代码示例。
本系列共分为四篇文章:
- 进程基础与基本操作
- 进程输入输出重定向
- 异步进程控制
- 进程间通信与最佳实践
目录
进程基础概念
什么是进程
进程(Process)是计算机中的一个基本概念,它是操作系统分配资源的基本单位,也是程序执行的实例。每个进程都有自己独立的内存空间、系统资源和执行环境。当你运行一个应用程序时,操作系统会为其创建一个或多个进程。
在现代操作系统中,进程具有以下特点:
- 独立性:每个进程都有自己的地址空间,不会直接访问其他进程的内存
- 并发性:多个进程可以并发执行,看起来像是同时运行
- 动态性:进程在执行过程中会经历创建、运行、等待、终止等不同状态
- 层次结构:进程可以创建子进程,形成父子关系
你是否思考过,当你双击一个应用程序图标时,操作系统背后发生了什么?实际上,操作系统会为该应用程序创建一个新的进程,分配必要的资源,然后开始执行程序代码。
Process 类介绍
在 C# 中,System.Diagnostics.Process 类是用于与本地和远程进程交互的主要工具。它提供了一组丰富的方法和属性,使开发者能够启动、监控和控制进程。
Process 类的主要功能包括:
-
启动和终止进程
Start():启动一个进程Kill():强制终止一个进程CloseMainWindow():向进程的主窗口发送关闭消息WaitForExit():等待进程结束
-
获取进程信息
Id:获取进程的唯一标识符ProcessName:获取进程的名称MachineName:获取进程运行的计算机名称StartTime:获取进程的启动时间TotalProcessorTime:获取进程使用的总处理器时间WorkingSet64:获取进程的物理内存使用量Responding:判断进程的主窗口是否响应用户输入
-
进程输入输出控制
StandardInput:获取进程的标准输入流StandardOutput:获取进程的标准输出流StandardError:获取进程的标准错误流
-
进程集合操作
GetProcesses():获取本地计算机上的所有进程GetProcessesByName():获取指定名称的所有进程GetProcessById():获取指定 ID 的进程
让我们看一个简单的例子,了解如何使用 Process 类获取当前运行的进程信息:
using System;
using System.Diagnostics;
class ProcessInfoExample
{
static void Main()
{
// 获取当前计算机上的所有进程
Process[] processes = Process.GetProcesses();
Console.WriteLine($"当前运行的进程数量: {processes.Length}");
Console.WriteLine("\nID\t进程名称\t\t内存使用(MB)");
Console.WriteLine("----------------------------------------");
// 显示每个进程的基本信息
foreach (Process process in processes)
{
try
{
// 将字节转换为兆字节
double memoryUsageMB = process.WorkingSet64 / 1024.0 / 1024.0;
Console.WriteLine($"{process.Id}\t{process.ProcessName}\t\t{memoryUsageMB:F2}");
}
catch (Exception ex)
{
// 某些系统进程可能无法访问
Console.WriteLine($"{process.Id}\t{process.ProcessName}\t\t访问受限");
}
}
}
}
运行这段代码,你将看到当前计算机上所有运行的进程列表,包括它们的 ID、名称和内存使用情况。这是一个简单但功能强大的示例,展示了如何使用 Process 类获取系统进程信息。

你有没有想过如何编写一个任务管理器类似的工具?上面的代码就是一个很好的起点。
进程与线程的区别
在讨论进程控制时,理解进程与线程的区别非常重要:
| 特性 | 进程 | 线程 |
|---|---|---|
| 定义 | 程序的执行实例,拥有独立的内存空间 | 进程内的执行单元,共享进程的内存空间 |
| 资源占用 | 较高,包括独立的内存空间和系统资源 | 较低,仅包含执行必需的资源 |
| 创建开销 | 较大 | 较小 |
| 通信方式 | 进程间通信(IPC)机制,如管道、套接字等 | 直接共享内存,通过同步机制通信 |
| 切换开销 | 较大 | 较小 |
| 隔离性 | 高,一个进程崩溃通常不会影响其他进程 | 低,一个线程崩溃可能导致整个进程崩溃 |
| C# 中的类 | System.Diagnostics.Process | System.Threading.Thread |
在实际应用中,我们通常使用进程来隔离不同的应用程序,而使用线程来实现单个应用程序内的并发执行。例如,当你需要执行一个外部程序时,你会创建一个新的进程;而当你需要在自己的应用程序中执行并行任务时,你会创建新的线程或使用任务并行库(TPL)。
你是否曾经思考过,为什么有些应用程序崩溃不会影响其他应用程序,而有些崩溃会导致整个系统不稳定?这与进程和线程的隔离性有关。
基本进程操作
掌握基本的进程操作是使用 C# 控制外部进程的基础。在这一部分,我们将学习如何启动进程、等待进程结束、终止进程以及获取进程信息。
启动进程
在 C# 中,启动一个新进程非常简单,主要有两种方式:
- 使用
Process.Start()静态方法 - 创建
ProcessStartInfo对象,然后使用Process.Start(ProcessStartInfo)方法
第一种方式适用于简单场景,而第二种方式提供了更多的配置选项。让我们看看这两种方式的示例:
方式一:使用 Process.Start() 静态方法
using System;
using System.Diagnostics;
class SimpleProcessStart
{
static void Main()
{
try
{
// 方式 1:直接启动一个应用程序
Process.Start("notepad.exe");
// 方式 2:启动应用程序并打开特定文件
Process.Start("notepad.exe", "test.txt");
// 方式 3:启动默认浏览器打开网址
Process.Start(new ProcessStartInfo
{
FileName = "https://www.baidu.com",
UseShellExecute = true // ✅ 允许使用操作系统外壳
});
Console.WriteLine("成功启动进程!");
}
catch (Exception ex)
{
Console.WriteLine($"启动进程时出错: {ex.Message}");
}
}
}
这种方式简单直观,但控制选项有限。对于需要更多配置的场景,我们可以使用 ProcessStartInfo 类。

方式二:使用 ProcessStartInfo 配置进程启动选项
using System;
using System.Diagnostics;
class AdvancedProcessStart
{
static void Main()
{
try
{
// 创建 ProcessStartInfo 对象
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "cmd.exe", // 要启动的程序
Arguments = "/c dir", // 命令行参数
WorkingDirectory = @"C:\Windows", // 工作目录
UseShellExecute = false, // 不使用操作系统 shell 启动
CreateNoWindow = true, // 不创建新窗口
RedirectStandardOutput = true, // 重定向标准输出
RedirectStandardError = true // 重定向标准错误
};
// 使用配置好的 ProcessStartInfo 启动进程
using (Process process = Process.Start(startInfo))
{
// 读取标准输出
string output = process.StandardOutput.ReadToEnd();
// 读取标准错误
string error = process.StandardError.ReadToEnd();
// 等待进程结束
process.WaitForExit();
// 获取退出代码
int exitCode = process.ExitCode;
Console.WriteLine("命令输出:");
Console.WriteLine(output);
if (!string.IsNullOrEmpty(error))
{
Console.WriteLine("错误输出:");
Console.WriteLine(error);
}
Console.WriteLine($"进程退出代码: {exitCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"启动进程时出错: {ex.Message}");
}
}
}

ProcessStartInfo 类提供了许多有用的属性,让你能够精确控制进程的启动方式:
| 属性 | 描述 |
|---|---|
FileName | 要启动的应用程序或文档的名称 |
Arguments | 启动应用程序时传递的命令行参数 |
WorkingDirectory | 进程的工作目录 |
UseShellExecute | 是否使用操作系统 shell 启动进程 |
CreateNoWindow | 是否创建新窗口 |
WindowStyle | 如果创建窗口,指定其显示状态(正常、最小化、最大化等) |
RedirectStandardInput | 是否重定向标准输入 |
RedirectStandardOutput | 是否重定向标准输出 |
RedirectStandardError | 是否重定向标准错误 |
Environment | 进程的环境变量集合 |
UserName | 启动进程的用户名 |
Password | 启动进程的密码 |
Domain | 启动进程的域 |
你是否注意到,当 UseShellExecute 设置为 false 时,我们可以使用重定向功能?这是因为直接启动进程(而不是通过 shell)时,我们可以直接访问进程的标准输入、输出和错误流。
等待进程结束
在许多情况下,我们需要等待启动的进程完成其工作。C# 提供了几种方式来实现这一点:
- 同步等待:使用
WaitForExit()方法 - 带超时的等待:使用
WaitForExit(int milliseconds)方法 - 异步等待:使用
WaitForExitAsync()方法(.NET 5+ 支持)
让我们看一个示例,展示如何等待进程结束并处理超时情况:
using System;
using System.Diagnostics;
class WaitForProcessExample
{
static void Main()
{
try
{
// 创建进程启动信息
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "www.baidu.com -n 5", // Ping 5 次
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true
};
Console.WriteLine("启动 ping 进程...");
// 启动进程
using (Process process = Process.Start(startInfo))
{
// 方式 1:无限期等待进程结束
// process.WaitForExit();
// 方式 2:等待进程结束,但设置超时时间(10秒)
bool exited = process.WaitForExit(10000);
if (exited)
{
string output = process.StandardOutput.ReadToEnd();
Console.WriteLine("进程已结束,输出:");
Console.WriteLine(output);
Console.WriteLine($"退出代码: {process.ExitCode}");
}
else
{
Console.WriteLine("进程未在指定时间内结束,准备强制终止...");
process.Kill();
Console.WriteLine("进程已被强制终止");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
}

在 .NET 5 及更高版本中,你还可以使用异步方式等待进程结束:
// .NET 5+ 支持
async Task WaitForProcessAsync()
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "www.baidu.com -n 5"
};
using (Process process = Process.Start(startInfo))
{
// 异步等待进程结束
await process.WaitForExitAsync();
Console.WriteLine("进程已结束");
}
}
你有没有遇到过需要等待外部程序完成处理的情况?例如,等待文件压缩工具完成压缩后再继续后续操作?上面的代码示例可以帮助你实现这类需求。
终止进程
有时,我们需要主动终止一个进程,例如当它运行时间过长或者不再响应时。C# 提供了几种方式来终止进程:
- Kill():立即终止进程,不给进程清理资源的机会
- CloseMainWindow():向进程的主窗口发送关闭消息,类似于点击窗口的关闭按钮
- Kill(bool entireProcessTree):终止进程及其子进程(.NET 5+ 支持)
下面是一个示例,展示如何优雅地关闭进程,如果失败则强制终止:
using System;
using System.Diagnostics;
using System.Threading;
class TerminateProcessExample
{
static void Main()
{
Process process = null;
try
{
// 启动记事本
process = Process.Start("notepad.exe");
if (process != null)
{
Console.WriteLine($"已启动记事本进程,ID: {process.Id}");
Console.WriteLine("等待 5 秒后尝试关闭进程...");
Thread.Sleep(5000);
// 方式 1:尝试优雅关闭(发送关闭消息到主窗口)
Console.WriteLine("尝试优雅关闭进程...");
bool closeSuccess = process.CloseMainWindow();
if (closeSuccess)
{
// 给进程一些时间来响应关闭请求
bool exited = process.WaitForExit(3000);
if (exited)
{
Console.WriteLine("进程已优雅关闭");
}
else
{
Console.WriteLine("进程未能在指定时间内关闭,准备强制终止");
process.Kill();
Console.WriteLine("进程已被强制终止");
}
}
else
{
Console.WriteLine("无法发送关闭消息,准备强制终止进程");
process.Kill();
Console.WriteLine("进程已被强制终止");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"操作进程时出错: {ex.Message}");
// 确保进程被终止
if (process != null && !process.HasExited)
{
try
{
process.Kill();
Console.WriteLine("进程已在异常处理中被终止");
}
catch
{
Console.WriteLine("无法终止进程");
}
}
}
finally
{
// 释放资源
process?.Dispose();
}
}
}

在 .NET 5 及更高版本中,你可以使用 Kill(bool entireProcessTree) 方法来终止进程及其所有子进程:
// .NET 5+ 支持
process.Kill(entireProcessTree: true);
你是否曾经遇到过"僵尸进程"?这些进程在任务管理器中显示为"无响应",必须强制终止。上面的代码示例展示了如何先尝试优雅关闭进程,如果失败再强制终止,这是处理此类情况的最佳实践。
获取进程信息
除了启动和终止进程外,我们还经常需要获取进程的详细信息。C# 的 Process 类提供了丰富的属性和方法来获取这些信息。
以下是一个示例,展示如何获取特定进程的详细信息:
using System;
using System.Diagnostics;
using System.Linq;
class ProcessInfoDetailedExample
{
static void Main()
{
try
{
// 获取所有 msedge 进程
Process[] msedgeProcesses = Process.GetProcessesByName("msedge");
if (msedgeProcesses.Length == 0)
{
Console.WriteLine("未找到 Edge进程,尝试启动一个...");
Process.Start("msedge.exe", "--new-window https://www.baidu.com");
// 给进程一些启动时间
System.Threading.Thread.Sleep(2000);
// 重新获取进程
msedgeProcesses = Process.GetProcessesByName("msedge");
}
if (msedgeProcesses.Length > 0)
{
// 获取第一个 msedge 进程的详细信息
Process msedgeProcess = msedgeProcesses[0];
Console.WriteLine("msedge 进程详细信息:");
Console.WriteLine($"进程 ID: {msedgeProcess.Id}");
Console.WriteLine($"进程名称: {msedgeProcess.ProcessName}");
Console.WriteLine($"主窗口标题: {msedgeProcess.MainWindowTitle}");
try
{
Console.WriteLine($"启动时间: {msedgeProcess.StartTime}");
Console.WriteLine($"运行时长: {DateTime.Now - msedgeProcess.StartTime}");
Console.WriteLine($"CPU 使用时间: {msedgeProcess.TotalProcessorTime}");
Console.WriteLine($"物理内存使用: {msedgeProcess.WorkingSet64 / 1024 / 1024} MB");
Console.WriteLine($"虚拟内存使用: {msedgeProcess.VirtualMemorySize64 / 1024 / 1024} MB");
Console.WriteLine($"线程数: {msedgeProcess.Threads.Count}");
Console.WriteLine($"句柄数: {msedgeProcess.HandleCount}");
Console.WriteLine($"优先级: {msedgeProcess.BasePriority}");
Console.WriteLine($"是否响应: {msedgeProcess.Responding}");
}
catch (Exception ex)
{
Console.WriteLine($"获取某些进程信息时出错: {ex.Message}");
}
// 获取进程的模块(加载的 DLL)
Console.WriteLine("\n加载的前 5 个模块:");
try
{
foreach (ProcessModule module in msedgeProcess.Modules.Cast<ProcessModule>().Take(5))
{
Console.WriteLine($" {module.ModuleName} - {module.FileName}");
}
}
catch (Exception ex)
{
Console.WriteLine($"获取模块信息时出错: {ex.Message}");
}
}
else
{
Console.WriteLine("无法找到或启动 msedge 进程");
}
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
}

这个示例展示了如何获取进程的各种详细信息,包括资源使用情况、线程数量、加载的模块等。这些信息对于监控和调试应用程序非常有用。
总结
在本文中,我们学习了 C# 中进程控制的基础知识和基本操作。我们了解了什么是进程,以及如何使用 System.Diagnostics.Process 类来启动、等待、终止和获取进程信息。这些基础知识为我们后续学习更高级的进程控制技术奠定了基础。
在下一篇文章中,我们将深入探讨进程输入输出重定向,学习如何与外部进程进行交互,包括向进程发送输入和获取进程的输出。
更多推荐



所有评论(0)