在 C# 开发中,并发编程主要涉及三个概念:进程 (Process)、线程 (Thread) 和 任务 (Task)。
进程是操作系统中正在运行的应用程序的实例。`Process` 类主要用于启动外部程序、打开文件或与操作系统进程交互。
使用 `Process.Start` 可以直接通过命令名称启动系统路径下的应用程序。
using System.Diagnostics; // 按钮事件示例 private void btnOpenNotepad_Click(object sender, EventArgs e) { // 打开记事本 Process.Start("notepad"); } private void btnOpenCalc_Click(object sender, EventArgs e) { // 打开计算器 Process.Start("calc"); } private void btnOpenPaint_Click(object sender, EventArgs e) { // 打开画图工具 Process.Start("mspaint"); } private void btnOpenUrl_Click(object sender, EventArgs e) { // 打开浏览器访问网址 // 注意:在 .NET Core/5+ 中可能需要设置 UseShellExecute = true Process.Start("iexplore", "http://www.baidu.com"); }
若要打开特定路径的文件(如 .txt, .docx)或文件夹,通常需要使用 `ProcessStartInfo` 来获得更精细的控制。
private void btnOpenFile_Click(object sender, EventArgs e) { // 实例化 Process 对象 Process process = new Process(); // 设置启动信息 // 方式 1:分步设置 // ProcessStartInfo startInfo = new ProcessStartInfo(@"C:\Users\Desktop\6666.txt"); // process.StartInfo = startInfo; // 方式 2:直接赋值 (推荐) process.StartInfo = new ProcessStartInfo(@"C:\Users\Desktop\6666.txt") { // 在 .NET Core / .NET 5+ 中,打开非可执行文件通常需要将此设为 true UseShellExecute = true }; // 执行打开 process.Start(); }
`Thread` 是 C# 中最基础的线程操作类。线程是进程中的执行单元。
using System.Threading; public void StartThreadDemo() { // 使用 Lambda 表达式创建线程 Thread thread = new Thread(() => { // 可以在这里加锁,但在这种简单结构中,锁(lock)主要用于保护共享资源 // lock (this) // { Console.WriteLine("线程开始工作..."); // 模拟耗时操作 Thread.Sleep(300); // 线程挂起(暂停)300毫秒 Console.WriteLine("线程工作结束。"); // } }); // --- 关键属性设置 --- // 设置为后台线程 // true: 主程序关闭时,此线程会被强制立即结束。 // false (默认): 主程序关闭时,必须等待此线程执行完毕,进程才会真正退出。 thread.IsBackground = true; // 启动线程 thread.Start(); // 阻塞主线程,直到 thread 线程执行完毕 (通常用于等待子线程结果) // thread.Join(); }
关于 Suspend 和 Resume 代码中提到的 `thread.Suspend()` (暂停) 和 `thread.Resume()` (恢复) 方法在现代 .NET 开发中已被标记为过时 (Obsolete) 且不建议使用。
原因:如果在持有锁或资源时强制暂停线程,极易导致死锁 (Deadlock)。建议使用 `ManualResetEvent` 或 `AutoResetEvent` 等信号量机制来控制线程的暂停与继续。
在 WinForms 等桌面应用中,子线程不能直接修改主线程(UI线程)创建的控件(如 TextBox, Label)。
暴力解决方法(仅用于测试,不推荐生产环境):
public Form1() { InitializeComponent(); // 取消跨线程非法访问检查 Control.CheckForIllegalCrossThreadCalls = false; }
推荐解决方法: 使用 `Invoke` 或 `BeginInvoke`。
线程优先级决定了 CPU 分配给该线程的时间片多少。优先级越高,被 CPU 调度的概率越大,但并不保证一定先执行完(取决于操作系统调度)。
Thread t = new Thread(MyMethod); // 优先级枚举 (ThreadPriority) t.Priority = ThreadPriority.Highest; // = 4 (最高) t.Priority = ThreadPriority.AboveNormal; // = 3 t.Priority = ThreadPriority.Normal; // = 2 (默认) t.Priority = ThreadPriority.BelowNormal; // = 1 t.Priority = ThreadPriority.Lowest; // = 0 (最低) t.Start();
理解“谁阻塞了谁”是多线程编程的关键。
| 方法 | 描述 | 谁被阻塞? | 状态 |
|---|---|---|---|
| Thread.Sleep(300) | 让当前正在执行代码的线程暂停指定时间。 | 自己 (当前线程) | 释放 CPU 时间片,指定时间后重新排队。 |
| thread1.Join() | 在线程 A 中调用 `thread1.Join()`。 | 调用者 (线程 A) | 线程 A 暂停执行,直到 `thread1` 的任务全部完成并退出,线程 A 才会继续往下走。 |
代码对比:
// 场景 1:Sleep (我休息一会) Console.WriteLine("开始休息"); Thread.Sleep(1000); // 主线程停止1秒 Console.WriteLine("休息结束"); // 场景 2:Join (我等你做完) Thread t = new Thread(() => { Thread.Sleep(5000); // 子线程工作5秒 }); t.Start(); Console.WriteLine("等待子线程..."); t.Join(); // 主线程卡在这里不动,直到 t 执行完毕 Console.WriteLine("子线程做完了,我继续。");
`Task` 是基于线程池 (`ThreadPool`) 的高级抽象。相比于 `Thread`,`Task` 性能更好(复用线程),API 更丰富(支持返回值、延续任务、取消等),是现代 C# 并发编程的首选。
`Task.Run` 是在线程池上运行 CPU 密集型代码的最快捷方式。
using System.Threading; using System.Threading.Tasks; public void TaskDemo() { // 启动一个新任务 Task.Run(() => { Console.WriteLine("Task 正在运行..."); // 模拟耗时操作 Thread.Sleep(300); Console.WriteLine("Task 运行结束"); }); // 注意:Task.Run 是异步的,主线程会继续立即往下执行。 // 如果需要等待 Task 完成,可以使用 await (在 async 方法中) 或 task.Wait()。 }
Thread 与 Task 的简单对比: