====== 线程与进程 (Threads & Processes) ====== 在 C# 开发中,并发编程主要涉及三个概念:**进程 (Process)**、**线程 (Thread)** 和 **任务 (Task)**。 * **引用命名空间**: * 进程:`using System.Diagnostics;` * 线程:`using System.Threading;` * 任务:`using System.Threading.Tasks;` ===== 进程 (Process) ===== 进程是操作系统中正在运行的应用程序的实例。`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) ===== `Thread` 是 C# 中最基础的线程操作类。线程是进程中的执行单元。 ==== Thread 基础案例 ==== 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` 等信号量机制来控制线程的暂停与继续。 ==== 跨线程 UI 操作 ==== 在 WinForms 等桌面应用中,子线程不能直接修改主线程(UI线程)创建的控件(如 TextBox, Label)。 **暴力解决方法(仅用于测试,不推荐生产环境):** public Form1() { InitializeComponent(); // 取消跨线程非法访问检查 Control.CheckForIllegalCrossThreadCalls = false; } **推荐解决方法:** 使用 `Invoke` 或 `BeginInvoke`。 ==== 线程优先级 (Priority) ==== 线程优先级决定了 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(); ==== 线程的阻塞 (Blocking) ==== 理解“谁阻塞了谁”是多线程编程的关键。 ^ 方法 ^ 描述 ^ 谁被阻塞? ^ 状态 ^ | **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) ===== `Task` 是基于线程池 (`ThreadPool`) 的高级抽象。相比于 `Thread`,`Task` 性能更好(复用线程),API 更丰富(支持返回值、延续任务、取消等),是现代 C# 并发编程的首选。 ==== Task.Run 案例 ==== `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 的简单对比:** * **Thread**: 是一辆私家车。每次出门(执行任务)都要现买一辆车(创建线程开销大),用完就报废(销毁)。 * **Task**: 是出租车。出门时打车(从线程池取线程),到了目的地后,车子不报废,而是去拉下一个乘客(线程复用,性能高)。