前言:
C#的应用软件中,经常要考虑到UI的相应和处理的程序(尤其占用时间很长的程序)之前的相应配合问题。
传统的思路,用线程的控制方法,加原子锁等方法,可是,再怎么搞都没有 windows C#自带的 BackgroundWorker
的方法更高效和稳定。
因为,`BackgroundWorker` 组件是实现异步编程的一种方式,它允许开发者在不阻塞主线程的情况下执行耗时操作,并通过事件与主线程安全地交互。
这个是windows内部,基于操作系统的工具。
在下面的参考代码里面,我们做了一个简单的带进度条的后台进程程序。
1 BackgroundWorker
的基本概念:
在C#中,
BackgroundWorker
是一个组件,用于在后台线程中执行长时间运行的任务,而不会影响用户界面的响应性。BackgroundWorker
允许你在后台线程中执行操作,同时可以安全地与主线程(通常是UI线程)通信。
1.2 属性和方法:
属性:
- `CancellationPending`: 一个只读属性,用于指示是否已经请求取消后台操作。如果用户请求取消操作,此属性将返回 `true`。
- `IsBusy`: 指示 `BackgroundWorker` 是否正在执行操作。
- `WorkerReportsProgress`: 用于标识,你是设置或获取 `BackgroundWorker` 报告进度更改。
- `WorkerSupportsCancellation`: 用于设置或获取 `BackgroundWorker` 是否支持取消操作。
事件:
- `DoWork`: 当 `RunWorkerAsync` 方法被调用时,此事件被触发。通常在此事件的处理程序中执行后台任务。
- `ProgressChanged`: 当 `ReportProgress` 方法被调用时,此事件被触发。可以在事件处理程序中更新用户界面以反映进度。
- `RunWorkerCompleted`: 当后台操作完成时,无论是正常完成、被取消还是发生错误,此事件都会被触发。
方法:(public)
- `CancelAsync`: 请求取消后台操作。如`WorkerSupportsCancellation` 属性设置为 `true`,此方法将触发 `CancellationPending` 属性变为 `true`。
- `ReportProgress`: 报告后台操作的进度。如 `WorkerReportsProgress` 属性设置为 `true`,可以使用此方法来更新进度指示器或执行其他与进度相关的操作。
- `RunWorkerAsync`: 启动后台操作
- `RunWorkerAsync (object argument)`: 带有参数的 `RunWorkerAsync` 方法。
受保护的虚方法:(只能在本背景类里面被调用,且可以重写和拓展)(protected)
- `OnDoWork(DoWorkEventArgs e)`: 当 `DoWork` 事件被触发时,此方法被调用。通常在此方法中实现后台任务的逻辑。
- `OnProgressChanged(ProgressChangedEventArgs e)`: 当 `ProgressChanged` 事件被触发时,此方法被调用。可以在此处更新进度信息。
- `OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)`: 当 `RunWorkerCompleted` 事件被触发时,此方法被调用。可以在此处处理后台操作完成后的清理工作或结果。
BackgroundWorker
具有的属性和方法如上,大多数是字面意思,我们读的懂,就可以用,我后面列举两个例子:
2 构建 BackgroundWorker
的方法:
2.1 加载
菜单: 视图\工具箱\组件\ 里面,可以找到BackgroundWorker
,然后,直接拖拽到你的主FROM
在主窗口的下沿:会出现定义。
2.2 属性
WorkerReportsProgress
(如果需要报告进度)和WorkerSupportsCancellation
(如果需要支持取消操作)
3 使用方法
3.3 背景Action的定义
3.3.1 DoWork方法
双击会自动进入背景处理程序DoWork方法:在这里写你的背景动作。
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // 在这里编写长时间运行的任务 }
当然,你双击的时候,绑定是自动的
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
3.4 过程进度和进度条的实现:
3.4.1 加入StatusStrip 状态栏:
3.4.2 加入进度条:
3.4.3 绑定到背景程序:
我们先看一下前面,1.2的属性和方法,和进度相关的有两个,一个是事件一个是报告进度,
- `WorkerReportsProgress`: 用于设置或获取 `BackgroundWorker` 是否报告进度更改。
- `ReportProgress`: 报告后台操作的进度。如 `WorkerReportsProgress` 属性设置为 `true`,可以使用此方法来更新进度指示器或执行其他与进度相关的操作。
- `OnProgressChanged(ProgressChangedEventArgs e)`: 当 `ProgressChanged` 事件被触发时,此方法被调用。可以在此处更新进度信息。
在绑定之前,我们先搞清楚,这3个接口的意义,
WorkerReportsProgress,用来表示是否需要报告进度。ReportProgress 方法(被调用的时候,当然,一般是在(背景任务中)DoWork中被调用。由于,WorkerReportsProgress = ture, ReportProgress 调用的时候会触发事件,OnProgressChanged,并把ReportProgress输入的进度的数值0到100,通过事件EvengArgs给到事件处理函数ProgressChanged。
3.5 启动任务
启动 BackgroundWorker的方法, RunWorkerAsync ,有时候可以带参数,object argument,object argument
参数非常有用,因为它允许你将数据传递给后台工作线程。
private void btnStart_Click(object sender, EventArgs e) { // 启动后台工作 backgroundWorker1.RunWorkerAsync(); }
3.6 完成任务事件
RunWorkerCompleted事件在后台程序完成或者取消的时候,会被触发
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // 检查后台工作是否成功完成 if (e.Error != null) { // 处理错误 MessageBox.Show("发生错误: " + e.Error.Message); } else if (e.Cancelled) { // 处理取消操作 MessageBox.Show("操作已取消。"); } else { // 使用 e.Result 获取 DoWork 事件处理器的结果 MessageBox.Show("操作完成: " + e.Result); } }
3.7 后台的取消:
3.7.1 CancellationPending 属性
指示是否已经请求取消后台操作
3.7.2 WorkerSupportsCancellation 属性
取消后台进程是否使能
3.7.3 请求取消后台方法:CancelAsync
WorkerSupportsCancellation == true,时候,将触发CancellationPending = true
4 参考代码:
在下面的参考代码里面,我们做了一个简单的带进度条的后台进程程序。
4.0 初始化:
【作者案】BackgroundWorker ,上面的定义的属性、事件、方法和接口,对于public的定义,我们可以之间用"."来调用,而protected的方法,需要进行实例的初始化。而初始化的时候,EventArgs需要传递的是Handler,例如:ProgressChangedEventArgs,对应ProgressChangedEventHandler ,在初始化的时候也可以绑定起来。
在Form的InitializeComponent定义里面,我们可以如下定义:
// // backgroundWorker1 // this.backgroundWorker1.WorkerReportsProgress = true; this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork); this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged); this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
4.1 启动后台
4.1.1 不带参数的启动
// 在窗体中,启动 BackgroundWorker 并传递文件路径 private void btnStartProcess_Click(object sender, EventArgs e) { if(!backgroundWorker1.IsBusy){ backgroundWorker1.RunWorkerAsync(); } }
通过IsBusy先判断,是否已经启动了,避免重复启动。
4.1.2 带参数的启动
// 在窗体中,启动 BackgroundWorker 并传递文件路径 private void btnStartProcess_Click(object sender, EventArgs e) { string filePath = "path/to/your/file.txt"; backgroundWorker1.RunWorkerAsync(filePath); }
【案】,注意: RunWorkerAsync的接口
4.2 后台执行任务
4.2.1 模拟进度更新:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { // 在这里编写长时间运行的任务 BackgroundWorker worker = sender as BackgroundWorker; for (int i = 0; i <= 100; i++) { // 模拟工作 Thread.Sleep(10); // 模拟耗时操作 // 报告进度 worker.ReportProgress(i); // 这里的 i 将作为 ProgressPercentage } }
4.2.2 模拟代入一个文件路径参数
// 在 BackgroundWorker 的 DoWork 事件处理器中使用参数 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; string filePath = e.Argument as string; // 从参数中获取文件路径 if (!worker.CancellationPending) { // 使用 worker 执行后台任务... if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) { // 使用文件路径执行后台任务 // 例如,读取文件内容并进行处理 string fileContent = File.ReadAllText(filePath); // ... 对文件内容进行处理 ... e.Result = "处理完成"; // 可以将结果设置在 e.Result 中,稍后在 RunWorkerCompleted 事件中使用 } else { e.Cancel = true; // 如果文件路径无效或文件不存在,取消操作 worker.ReportProgress(0, "文件路径无效或文件不存在。"); } } }
BackgroundWorker worker = sender as BackgroundWorker;
这行代码的目的是将事件的
sender
参数转换为BackgroundWorker
类型,以便使用BackgroundWorker
类的成员。如果转换成功,worker
将是一个指向原始BackgroundWorker
实例的引用
string filePath = e.Argument as string; // 从参数中获取文件路径
4.3 后台完成
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { MessageBox.Show("操作已取消。"); } else if (e.Error != null) { MessageBox.Show("发生错误: " + e.Error.Message); } else { // 使用 DoWork 事件处理器的结果 MessageBox.Show("操作结果: " + e.Result); } }
5 运行结果展示:
我在Form的底部加了一个进度条,然后,运行后台模拟进度后,显示如下: