====== 线程与进程 (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**: 是出租车。出门时打车(从线程池取线程),到了目的地后,车子不报废,而是去拉下一个乘客(线程复用,性能高)。