解剖 .NET 經典:從 Component 到 BackgroundWorker

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開發者來說…