1️⃣ 背景与定位

在 .NET Framework 2.0 时代,微软引入了 BackgroundWorker 来解决 WinForm/WPF 场景下“耗时操作阻塞 UI 线程”的问题;而 Component 早在 1.0 就已存在,是所有可视化/非可视化设计器的“基类”。理解这两者的源码与机制,可以帮助我们:

  • 更精确地调试遗留代码;

  • 在需要设计时支持时(如自定义控件/组件)少走弯路;

  • 在无法使用 async-await 的老项目中做最小侵入式改造。

2️⃣ Component 类:所有“组件”的鼻祖 

2.1 命名空间与程序集

// 所在程序集:System.ComponentModel.Primitives.dll
namespace System.ComponentModel

2.2 继承链与接口实现

System.Object└─ System.MarshalByRefObject   // 支持跨 AppDomain 远程调用└─ System.ComponentModel.Component├─ 实现 IComponent    // 提供 Site 与 Disposed 事件└─ 实现 IDisposable   // 标准 Dispose 模式

2.3 设计时元数据

  • [ComVisible(true)]:可被 COM 调用。

  • [ClassInterface(ClassInterfaceType.AutoDispatch)]:为 COM 客户端生成双重接口。

  • [DesignerCategory("Component")]:告诉 Visual Studio 使用 ComponentDesigner。

2.4 核心字段与线程安全 

private static readonly object EventDisposed = new object();
private ISite site;
private EventHandlerList events; // 延迟创建,减少内存
  • 所有事件都通过 EventHandlerList 存储,避免每个事件一个字段,节省内存。

  • events 的访问采用“延迟初始化 + 双检锁”。

2.5 事件系统:EventHandlerList 的魔法 

public event EventHandler Disposed
{add    { Events.AddHandler(EventDisposed, value); }remove { Events.RemoveHandler(EventDisposed, value); }
}
  • 通过 object 类型的 key(而非字符串)避免冲突。

  • EventHandlerList 内部使用单向链表,添加/删除 O(1)。

2.6 Dispose 模式深度解析 

~Component() { Dispose(false); }public void Dispose()
{Dispose(true);GC.SuppressFinalize(this);
}protected virtual void Dispose(bool disposing)
{if (!disposing) return;lock (this){if (site?.Container != null)site.Container.Remove(this); // 设计器撤销时自动清理((EventHandler)events?[EventDisposed])?.Invoke(this, EventArgs.Empty);}
}
  • 标准 Dispose 模式 + lock(this) 保证线程安全。

  • 在 VS 设计器中,当用户从设计面板上删除组件时,Container.Remove 会触发 Dispose

2.7 设计时支持:Site / Container / DesignMode 

  • ISite:为组件提供“宿主”信息(名称、容器、设计模式标志、全局服务)。

  • IContainer:管理一组组件的生命周期。

  • DesignMode:运行时永远返回 false,仅在设计器进程返回 true。

2.8 实战:自定义一个可设计时拖拽的组件 

[ToolboxItem(true)]
public class MyLogger : Component
{public string LogPath { get; set; }public void Write(string text){File.AppendAllText(LogPath, text + Environment.NewLine);}protected override void Dispose(bool disposing){// 清理文件句柄等资源base.Dispose(disposing);}
}
  • 编译后将 dll 添加到工具箱即可拖拽到 WinForm 设计器。

  • 可在属性窗口编辑 LogPath

3️⃣ BackgroundWorker 类:WinForm/WPF 时代的“轻量 Task” 

3.1 背景与演进史

  • .NET 2.0 引入,解决 WinForm 中“跨线程访问 UI”痛点。

  • .NET 4.0 之后官方推荐使用 Task + Progress<T>

  • 但在老项目、设计器生成代码、脚本环境中仍大量存在。

3.2 类型声明与特性 

[SRDescription("BackgroundWorker_Desc")]      // 本地化资源
[DefaultEvent("DoWork")]                      // 双击控件默认生成 DoWork 事件
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
  • HostProtection 告诉 CLR:若宿主禁止共享状态(如 SQL Server CLR),则拒绝加载。

3.3 三大静态键对象 

private static readonly object doWorkKey          = new object();
private static readonly object runWorkerCompletedKey = new object();
private static readonly object progressChangedKey = new object();
  • 用于 Events.AddHandler/remove,保证事件 key 的唯一性。

3.4 状态字段:isRunning、cancellationPending 的并发语义 

  • 均为 bool,读写均在 UI 线程通过 AsyncOperation.Post 封送,因此无需 volatileInterlocked

  • 注意:在 DoWork 事件处理器中访问 CancellationPending 是线程安全的,因为 CancelAsync 仅设置标志位,无额外锁。

3.5 AsyncOperation & SynchronizationContext 

  • AsyncOperationManager.CreateOperation(null) 捕获当前 SynchronizationContext(WinForm/WPF Dispatcher)。

  • 通过 Post / PostOperationCompleted 把回调封送回 UI 线程,避免跨线程更新控件。

3.6 三大事件 

事件名触发线程典型用途
DoWork后台线程
执行耗时计算、IO
ProgressChangedUI 线程(SynchronizationContext)更新进度条、日志
RunWorkerCompletedUI 线程处理结果或异常、隐藏等待动画

3.7 四大公开方法

RunWorkerAsync(object argument)

  • 检查 isRunning,抛 InvalidOperationException 防并发。

  • 创建 AsyncOperation,然后 threadStart.BeginInvoke(argument, null, null) 进入线程池。

CancelAsync() 

  • 检查 WorkerSupportsCancellation,设置 cancellationPending = true

ReportProgress(int percent, object userState) 

  • 检查 WorkerReportsProgress,通过 asyncOperation.Post(progressReporter, args) 封送。

Dispose(bool)(继承自 Component) 

  • 在 WinForm 设计器中移除控件时会调用,确保线程清理。

3.8 两大回调委托 

private readonly WorkerThreadStartDelegate threadStart;      // 线程入口
private readonly SendOrPostCallback operationCompleted;      // 完成回调
private readonly SendOrPostCallback progressReporter;        // 进度回调
  • WorkerThreadStartDelegate 本质上是 delegate void WorkerThreadStartDelegate(object argument),避免每次构造新委托。

3.9 WorkerThreadStart 流程图 

 

 3.10 线程模型与死锁陷阱

  • DoWork阻塞 UI 线程(如 Invoke 等待 UI 回复),将发生死锁。

  • 正确做法:仅通过 ReportProgress 与 UI 交互。

 3.11 性能考量:与 Task 对比

维度BackgroundWorkerTask + Progress<T>
语言级 async-await
线程池调度
进度报告事件IProgress<T>
取消
CancelAsync
CancellationToken
内存分配
事件包装对象
结构体 + lambda

结论:新项目优先 Task;维护老代码可保留 BGW。

3.12 跨平台注意点

 

  • .NET Core 3.0+ 仍支持 BGW,但设计器(WinForm Designer)仅在 Windows 上可用。

  • ASP.NET Core 无 SynchronizationContext,默认会把回调丢到 ThreadPool,UI 更新需手动封送。

3.13 实战:文件复制器(含 WinForm UI) 

  1. 设计界面:ProgressBar、TextBox(文件名)、Button(开始/取消)。

  2. 代码:

    var bgw = new BackgroundWorker
    {WorkerReportsProgress = true,WorkerSupportsCancellation = true
    };bgw.DoWork += (_, e) =>
    {var src = (string)e.Argument;var dst = Path.ChangeExtension(src, ".bak");using var fin = File.OpenRead(src);using var fout = File.Create(dst);var buffer = new byte[4096];long total = fin.Length, read = 0;int count;while ((count = fin.Read(buffer, 0, buffer.Length)) > 0){if (bgw.CancellationPending) { e.Cancel = true; return; }fout.Write(buffer, 0, count);read += count;bgw.ReportProgress((int)(read * 100 / total));}e.Result = dst;
    };bgw.ProgressChanged += (_, e) => progressBar1.Value = e.ProgressPercentage;
    bgw.RunWorkerCompleted += (_, e) =>
    {if (e.Cancelled) MessageBox.Show("已取消");else if (e.Error != null) MessageBox.Show(e.Error.Message);else MessageBox.Show("完成,输出:" + e.Result);
    };
    bgw.RunWorkerAsync(textBox1.Text);

    3.14 在 ASP.NET Core 中的“逆向”用法

 虽然不推荐,但可在后台服务中模拟:

services.AddHostedService(sp => new BgwHostedService());
  • 由于没有 UI SynchronizationContext,所有 ProgressChanged 会在 ThreadPool 线程触发,需自行加锁。

4️⃣ 可维护性最佳实践 

  1. 永远检查 IsBusy,避免多次 RunWorkerAsync

  2. DoWork绝不 直接访问 UI 元素。

  3. 若业务复杂,考虑用 Task + Progress + CancellationTokenSource 重构。

  4. 设计时组件务必重写 Dispose(bool) 释放非托管资源。

  5. 使用 SRDescription/SRCategory 为你的组件提供本地化与分类支持。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91770.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91770.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91770.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

桌面端界面设计 |货物 TMS 系统 - SaaS UI UX 设计:审美积累之境

在物流数字化的浪潮中&#xff0c;货物 TMS 系统的 SaaS 化与 UI/UX 设计正构建着独特的审美坐标系。这不仅是技术与功能的融合&#xff0c;更是一场关于效率美学的深度探索&#xff0c;为行业审美积累注入了鲜活的实践样本。SaaS 模式赋予货物 TMS 系统轻盈而强大的特质&#…

多架构镜像整合全攻略:在Docker中实现单一镜像支持同时支持amd64和arm64架构

多架构支持的挑战 &#xff1a;随着异构计算&#xff08;如 ARM、x86、RISC-V 等&#xff09;的普及&#xff0c;开发者需要为不同硬件平台提供对应的镜像&#xff0c;传统方式需维护多个版本&#xff08;如 image:v1-amd64 和 image:v1-arm64 &#xff09;&#xff0c;导致版本…

Linux730 tr:-d /-s;sort:-r,-n,-R,-o,-t,-k,-u;bash;cut:-d,-c;tee -a;uniq -c -i

回顾 sort sort [选项] 文件-u&#xff1a;唯一&#xff0c;去除重复 -r:按数字大小&#xff0c;倒序排序&#xff0c;大到小 -o:输出文件 -n:按数字大小&#xff0c;顺序排序&#xff0c;小到大 -t: -t后加分割符&#xff0c;按分割符为标准&#xff0c;进行筛选 -k:k后加数字…

力扣457:环形数组是否存在循环

力扣457:环形数组是否存在循环题目思路代码题目 存在一个不含 0 的 环形 数组 nums &#xff0c;每个 nums[i] 都表示位于下标 i 的角色应该向前或向后移动的下标个数&#xff1a; 如果 nums[i] 是正数&#xff0c;向前&#xff08;下标递增方向&#xff09;移动 |nums[i]| 步…

在 Elasticsearch 中落地 Learning to Rank(LTR)

1 为什么要引入 LTR&#xff1f; 常规检索&#xff08;BM25、语义检索、Hybrid、RRF …&#xff09;往往只能基于少量信号&#xff08;关键词命中、向量相似度&#xff09;排序。 Learning-to-Rank 通过机器学习模型把多维度特征&#xff08;文档属性、查询属性、查询-文档相关…

Socket编程——TCP协议

文章目录一、TCP传输二、相关接口三、多进程版本四、多线程版本一、TCP传输 TCP和UDP类似&#xff0c;但是在传输中TCP有输入&#xff0c;输出缓冲区&#xff0c;看下面的传输图片 可以理解为TCP之间的数据传输都是依赖各自的socket&#xff0c;socket就充当传输的中介吧。 而…

GitHub使用小记——本地推送、外部拉取和分支重命名

GitHub 项目推送与拉取等操作使用随记 本小记适用于个人项目或组织项目&#xff0c;涵盖 GitHub 推送、拉取、分支管理、.gitignore 设置等常见需求。 1. 将已有本地工程推送至 GitHub 新仓库 1.1 前提条件 本地项目结构完整&#xff0c;已准备好&#xff1b;本地已安装 Git…

RabbitMQ 延时队列插件安装与使用详解(基于 Delayed Message Plugin)

RabbitMQ 延时队列插件安装与使用详解&#xff08;基于 Delayed Message Plugin&#xff09;&#x1f4cc; 一、什么是 RabbitMQ 延时队列&#xff1f;&#x1f680; 二、安装前准备✅ RabbitMQ 环境要求&#x1f527; 三、安装延时队列插件&#x1f9e9; 插件名称&#xff1a;…

Vue项目使用ssh2-sftp-client实现打包自动上传到服务器(完整教程)

告别手动拖拽上传&#xff01;本教程将手把手教你如何通过ssh2-sftp-client实现Vue项目打包后自动上传到服务器&#xff0c;提升部署效率300%。&#x1f680;一、需求场景与解决方案在Vue项目开发中&#xff0c;每次执行npm run build后都需要手动将dist目录上传到服务器&#…

《质光相济:Three.js中3D视觉的底层交互逻辑》

在Three.js搭建的虚拟维度中,光照与材质的关系远非技术参数的简单叠加,当光线以数字形态穿越虚空,与物体表面相遇的瞬间,便开始书写属于这个世界的物理叙事——每一缕光斑的形状、每一块阴影的浓淡、每一寸肌理的反光,都是对现实光学规律的转译与重构。理解这种交互的深层…

无刷电机在汽车领域的应用与驱动编程技术

文章目录引言一、核心应用场景1. 新能源汽车动力系统2. 底盘控制系统3. 车身与舒适系统4. 智能驾驶与安全系统二、无刷电机的技术优势解析三、无刷电机驱动编程基础1. 驱动原理2. 驱动架构四、核心控制算法与实现1. 六步换向法&#xff08;梯形波控制&#xff09;算法流程图C语…

【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心

游戏引擎开发记录&#xff1a;2024年 5月20日-6月4日&#xff1a;攻克2D物理引擎。 2024年 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 2024年 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 2024年 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 2024年…

kotlin kmp 跨平台环境使用sqldelight

欢迎访问我的主页: https://heeheeaii.github.io/ 1. 项目结构 SQLDelightKMPDemo/ ├── shared/ │ ├── src/ │ │ ├── commonMain/kotlin/ │ │ ├── androidMain/kotlin/ │ │ ├── desktopMain/kotlin/ │ │ └── commonMain/sqldel…

机器学习【五】decision_making tree

决策树是一种通过树形结构进行数据分类或回归的直观算法&#xff0c;其核心是通过层级决策路径模拟规则推理。主要算法包括&#xff1a;ID3算法基于信息熵和信息增益选择划分属性&#xff1b;C4.5算法改进ID3&#xff0c;引入增益率和剪枝技术解决多值特征偏差&#xff1b;CART…

简单记录一下VSCode中的一些学习记

在刚开始学习VSCode时&#xff0c;相信大家都会好奇VSCode底部区域那几个不同的状态栏具体有什么作用&#xff08;输出、调试控制台、终端、端口&#xff09;&#xff0c;貌似好像都是输出与代码相关的信息的&#xff1f;貌似代码运行结果既可以出现在输出中&#xff0c;也可以…

基于 Hadoop 生态圈的数据仓库实践 —— OLAP 与数据可视化(二)

目录 二、Hive、SparkSQL、Impala 比较 1. SparkSQL 简介 2. Hive、SparkSQL、Impala 比较 &#xff08;1&#xff09;功能 &#xff08;2&#xff09;架构 &#xff08;3&#xff09;场景 3. Hive、SparkSQL、Impala 性能对比 &#xff08;1&#xff09;cloudera 公司…

C++:std::array vs 原生数组 vs std::vector

&#x1f4cc; C&#xff1a;std::array vs 原生数组 vs std::vector 引用&#xff1a; C/C 标准库 std::vector、std::array、原生静态数组 的区别有哪些&#xff1f; 深度剖析&#xff1a;std::vector 内存机制与 push_back 扩容策略 今天过去了 还有许许多个明天 能和大…

Hyper-V + Centos stream 9 搭建K8s集群(二)

一、安装自动补全主节点安装就可以yum install -y bash-completion echo source <(kubectl completion bash) >>~/.bashrc kubectl completion bash >/etc/bash_completion.d/kubectl二、安装Calico网络插件&#xff08;主节点&#xff09;下载文件wget https://ca…

VBA代码解决方案第二十七讲:禁用EXCEL工作簿右上角的关闭按钮

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

Spring AI 系列之三十一 - Spring AI Alibaba-基于Nacos的MCP

之前做个几个大模型的应用&#xff0c;都是使用Python语言&#xff0c;后来有一个项目使用了Java&#xff0c;并使用了Spring AI框架。随着Spring AI不断地完善&#xff0c;最近它发布了1.0正式版&#xff0c;意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说…