.NET物件與Windows控制代碼(二):控制代碼分類和.NET控制代碼洩露的例子
上一篇文章介紹了控制代碼的基本概念,也描述了C#中建立檔案控制代碼的過程。我們已經知道控制代碼代表Windows內部物件,檔案物件就是其中一種,但顯然系統中還有更多其它型別的物件。本文將簡單介紹Windows物件的分類。
控制代碼可以代表的Windows物件分為三類,核心物件(Kernel Object)、使用者物件(GDI Object)和GDI物件,上一篇文章中工作管理員中的“控制代碼數”、“使用者物件”和“GDI物件”計數就是與這幾類物件對應的。為什麼要這樣分類呢?原因就在於這幾類物件對於作業系統而言有不同的作用,管理和引用的方式也不同。核心物件主要用於記憶體管理、程序執行以及程序間通訊,使用者物件用於系統的視窗管理,而GDI物件用來支援圖形介面。
一、觀察控制代碼變化的小實驗
在列舉Windows物件的分類之前,我們再看一個關於控制代碼數量的實驗,與之前檔案物件的控制代碼不同,本例中的控制代碼屬於使用者物件。程式執行過程中,物件的建立和銷燬是動態進行的,控制代碼數量也隨之動態變化,即使是一個最簡單的Windows Form程式也可以直觀的反映這一點。下圖是一個只有文字框和按鈕的窗體程式,程式啟動後預設輸入焦點在文字框上,可以按下Tab鍵將焦點在文字框和按鈕之間交替切換。當我們這樣做時,在工作管理員中可以看到:使用者物件的數量在21和20之間不斷變化。這一數字在你的執行環境中可能不同,但至少說明在焦點切換過程中有一個使用者物件在不斷的被建立銷燬,這個物件就是Caret(插入符號)。
Caret是使用者物件的一種,這個閃爍的游標指示輸入的位置。我們可以通過Windows API建立這個符號,定製它的樣式,也可以設定閃爍時間。建立Caret時,Windows API並不返回它的控制代碼,原因是一個視窗只能顯示一個插入符號,可以通過視窗的控制代碼對它進行訪問,或者更簡單的,看哪個執行緒在呼叫這些API即可。但無論如何,Caret物件和其控制代碼是真實存在的,即便我們不需要獲取這個控制代碼。
二、Windows物件的分類
前面提到了Windows物件分為核心物件、使用者物件和GDI物件,也舉了檔案物件和Caret物件的例子,除此之外還有很多其它型別的物件。Windows物件的完整列表,可以參考MSDN中關於
核心物件:訪問令牌、更改通知、通訊裝置、控制檯輸入、控制檯螢幕緩衝區、桌面、事件、事件日誌、檔案、檔案對映、堆、作業、郵件槽、模組、互斥量、管道、程序、訊號量、套接字、執行緒、定時器、定時器佇列、定時器佇列定時器、更新資源和視窗站。
使用者物件:加速鍵表、插入符號、游標、動態資料交換會話、鉤子、圖示、選單、視窗和視窗位置。
GDI物件:點陣圖、畫刷、裝置上下文、增強型圖元檔案、增強型圖元檔案裝置上下文、字型、記憶體裝置上下文、圖元檔案、圖元檔案裝置上下文、調色盤、畫筆和區域。
如前所述,不同類別的物件具有不同的作用和特點。核心物件主要用於記憶體管理、程序執行以及程序間通訊。多個程序可以共用同一個核心物件(如檔案和事件),但每個程序必須獨自建立或開啟這個物件以獲取自己的控制代碼,並指定不同的訪問許可權,這種情況下,一個核心物件會被多個程序的控制代碼引用;使用者物件用於系統的視窗管理,與核心物件不同的是,一個使用者物件僅能有一個控制代碼,但控制代碼是對其它程序公開的,因此其它程序可以獲取並使用這個控制代碼來訪問使用者物件。以視窗(Windows)物件為例,一個程序可以獲取另一個程序建立的視窗物件的控制代碼,並向其傳送各種訊息,這也是很多自動化測試工具得以實現的前提;而GDI物件用來支援圖形介面,也只支援單個物件單個控制代碼,但與使用者物件不同的是,GDI物件的控制代碼是程序私有的。
三、與Windows物件對應的.NET物件
.NET中有不少型別封裝了上面所列舉Windows物件,我們在使用時要特別注意對這些物件的進行重用和適時銷燬。下表是一些對應關係的例子(注意這不是完整列表,也並非嚴格的一一對應關係),後續文章將會討論其中一些重要型別的用法。
.NET物件 |
引用到的Windows物件控制代碼 |
分類 |
System.Threading.Tasks.Task |
訪問令牌 |
核心物件 |
System.IO.FileSystemWatcher |
更改通知 |
核心物件 |
System.IO.FileStream |
檔案 |
核心物件 |
System.Threading.AutoResetEvent |
事件 |
核心物件 |
System.Diagnostics.EventLog |
事件日誌 |
核心物件 |
System.Threading.Thread |
執行緒 |
核心物件 |
System.Threading.Mutex |
互斥量 |
核心物件 |
System.Threading.Semaphore |
訊號量 |
核心物件 |
System.Windows.Forms.Cursor |
游標 |
使用者物件 |
System.Drawing.Icon |
圖示 |
使用者物件 |
System.Windows.Forms.Menu |
選單 |
使用者物件 |
System.Windows.Forms.Control |
視窗 |
使用者物件 |
System.Windows.Forms.Control |
點陣圖 |
GDI物件 |
System.Drawing.SolidBrush |
畫刷 |
GDI物件 |
System.Drawing.Font |
字型 |
GDI物件 |
四、.NET中與控制代碼洩露相關的異常和現象
上一篇文章提到了控制代碼的限制,當程序或系統的控制代碼數量達到上限時,程式執行就會出現異常。常見的錯誤是System.ComponentModel.Win32Exception的“Error creating window handle”,或者“儲存空間不足,無法處理此命令”等,錯誤出現時記憶體往往也會有顯著增長。如果是達到了系統級別的控制代碼上限,其它程式的執行也受到影響,系統可能無法開啟任何新的選單和視窗、視窗也會出現繪製不完整的情況。這時及時抓取Dump並終止洩露控制代碼的程序,系統往往立即恢復正常。
五、第一個控制代碼洩露的例子
下面的示例程式碼包含控制代碼洩露的問題,為了演示方便,實現程式碼被最簡單化,設計的合理性也暫且不作深究。程式碼模擬了一個應用場景:程式包含一個DataReceiver不斷從某個資料來源獲取實時資料,DataReceiver同時會啟動一個DataAnalyzer,定時分析這些資料。設想程式有一個專門的子視窗來顯示這些資料,當子視窗被臨時關閉時,資料的實時獲取和分析過程也可以暫時終止。程式長時間執行的過程中,子視窗可能被使用者多次關閉和開啟,因此DataReceiver會被建立多次,程式啟動後的程式碼模擬DataReceiver被建立和Dispose了1000次。
using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Timer = System.Threading.Timer; namespace LeakExample { public partial class Form1 : Form { public Form1() { InitializeComponent(); // 模擬程式執行過程中多次建立DataReceiver的情況 Task.Factory.StartNew(() => { for (int i = 0; i < 1000; i++) { using (IDisposable receiver = new DataReceiver()) { Thread.Sleep(100); } } }); } } public class DataReceiver : IDisposable { private Timer dataSyncTimer = null; private IAnalyzer analyzer = null; private bool isDisposed = false; public DataReceiver() : this(new DataAnalyzer()) { } public DataReceiver(IAnalyzer dataAnalyzer) { dataSyncTimer = new Timer(GetData, null, 0, 500); analyzer = dataAnalyzer; analyzer.Start(); } private void GetData(object state) { // 獲取資料並放入快取 } public void Dispose() { if (isDisposed) return; if (dataSyncTimer != null) { dataSyncTimer.Dispose(); } isDisposed = true; } } public interface IAnalyzer { void Start(); void Stop(); } public class DataAnalyzer : IAnalyzer { private Timer analyzeTimer = null; public void Start() { analyzeTimer = new Timer(DoAnalyze, null, 0, 1000); } public void Stop() { if (analyzeTimer != null) { analyzeTimer.Dispose(); } } private void DoAnalyze(object state) { // 從快取中取得資料並分析,耗時600毫秒 Thread.Sleep(600); } } }
當執行這段程式時,可以從工作管理員觀察到控制代碼數持續增長,最終基本穩定在某一個較高的數字。雖然DataReceiver被多次建立,但控制代碼數的增長最終遠遠超過其被建立的次數。由於程式碼簡單,你很可能已經看出問題所在,然而在實際的專案中,由於軟體架構和業務邏輯程式碼更為複雜,很難一眼就看出問題的根源。下一篇文章將從這個例子入手,結合一些工具來分析問題存在的原因,並討論Timer是如何工作的。
相關推薦
.NET物件與Windows控制代碼(二):控制代碼分類和.NET控制代碼洩露的例子
上一篇文章介紹了控制代碼的基本概念,也描述了C#中建立檔案控制代碼的過程。我們已經知道控制代碼代表Windows內部物件,檔案物件就是其中一種,但顯然系統中還有更多其它型別的物件。本文將簡單介紹Windows物件的分類。 控制代碼可以代表的Windows物件分為三類,核心物件(Kernel Object)、
建造者模式-Builder Pattern 複雜物件的組裝與建立——建造者模式(二):遊戲角色設計的建造者模式解決方案
8.3 完整解決方案 Sunny公司開發人員決定使用建造者模式來實現遊戲角色的建立,其基本結構如圖8-3所示: 圖8-3 遊戲角色建立結構圖 在圖8-3中,Ac
Windows Service 學習系列(二):C# windows服務:安裝、解除安裝、啟動和停止Windows Service
一、通過CMD安裝、解除安裝、啟動、停止Windows Service 方法一 1.以管理員身份執行cmd 2.安裝windows服務 切換cd C:\Windows\Microsoft.NET\Framework\v4.0.30319(InstallUtil.e
DataGridView控制元件用法(二):為每行記錄最後加“編輯”-“刪除”按鈕列
1. 在中已經顯示出列表資料,這時我們需要對每行資料記錄進行編輯,需要新增“編輯”、“刪除”、“檢視”這樣的超連結。程式碼如下: 1 //為每行資料增加編輯列。 2 //設定列不能自動作成 3 UserdataGridView.AutoGenerate
機器學習與神經網路(二):感知器的介紹和Python程式碼實現
前言:本篇博文主要介紹感知器的相關知識,採用理論+程式碼實踐的方式,進行感知器的學習。本文首先介紹感知器的模型,然後介紹感知器學習規則(Perceptron學習演算法),最後通過Python程式碼實現單層感知器,從而給讀者一個更加直觀的認識。 1.單層感知器模型 單層感知器
用Java實現JVM(二):支援介面、類和物件
1. 概述我的 JVM 已經能夠執行HelloWorld了,並且有了基本的 JVM 骨架,包括執行時資料結構的定義(棧、棧幀、運算元棧等),執行時的邏輯控制等。但它還沒有類和物件的概念,比如無法執行下面這更復雜的HelloWorld:public interface SpeakerInterface {
軟體安裝集合(二):linux埠訪問telnet和nc安裝和使用
一、實現功能 兩個linux常用的埠資料傳送軟體的安裝和配置 二、安裝和配置 1.telnet (1)安裝 yum -y install telnet (2)使用 telnet ibeifeng.com 44444 2.nc (1)安裝 yum inst
零基礎從頭學習Swift(二):Swift中的變數和常量
個人部落格站已經上線了,網址 www.llwjy.com ~歡迎各位吐槽~-------------------------------------------------------------------------------------------------
NVIDIA Jetson TX2使用筆記(二):使用JetPack刷機和安裝Package
NVIDIA JetPack SDK is the most comprehensive solution for building AI applications. Use the JetPack installer to flash your Jetson Developer Kit with the
API 系列教程(二):結合 Laravel 5.5 和 Vue SPA 基於 jwt-auth 實現 API 認證
上一篇我們簡單演示了 Laravel 5.5 中 RESTful API 的構建、認證和測試,本教程將在上一篇教程的基礎上進行昇華。 我們將結合 Laravel 和 Vue 單頁面應用(SPA),在它們的基礎上引入 jwt-auth 實現 API 認證,由於 Laravel 集成了對 Vue
機器學習(二):快速入門SVM分類
定義 SVM便是根據訓練樣本的分佈,搜尋所有可能的線性分類器中最佳的那個。仔細觀察彩圖中的藍線,會發現決定其位置的樣本並不是所有訓練資料,而是其中的兩個空間間隔最小的兩個不同類別的資料點,而我們把這種可以用來真正幫助決策最優線性分類模型的資料點稱為”支
【H.264/AVC視訊編解碼技術詳解】十五、H.264的變換編碼(二):H.264整數變換和量化的實現
《H.264/AVC視訊編解碼技術詳解》視訊教程已經在“CSDN學院”上線,視訊中詳述了H.264的背景、標準協議和實現,並通過一個實戰工程的形式對H.264的標準進行解析和實現,歡迎觀看! “紙上得來終覺淺,絕知此事要躬行”,只有自己按照標準文件以程式碼
Java分散式跟蹤系統Zipkin(二):Brave原始碼分析-Tracer和Span
Brave是Java版的Zipkin客戶端,它將收集的跟蹤資訊,以Span的形式上報給Zipkin系統。 (Zipkin是基於Google的一篇論文,名為Dapper,Dapper在荷蘭語裡是“勇敢的”的意思,這也是Brave的命名的原因) 我們一般
.NET物件與Windows控制代碼:控制代碼的基本概念
在.NET程式設計中,得益於有效的記憶體管理機制,物件的建立和使用比較方便,大多數情況下我們無須關心物件建立和分配記憶體的細節,也可以放心的把物件的清理交給自動垃圾回收來完成。由於.NET類庫對系統底層物件進行了封裝,我們也不需要呼叫Windows API來操作非託管物件。但
git 在windows下的應用(二) - 遠程倉庫代碼管理
軟件研發 研發管理 克隆遠程git 目錄https://github.com/pcdogyu/git4windows.git克隆下來了生成1.txtscan stage signoff commit2次提交記錄推送到遠程地址完成了還沒來得及去網頁呢,就收到系統提示郵件網頁查看1.txt已經提交上去了對
ASP.NET MVC5(二):控制器、視圖與模型
script pcr 靜態 簡單 err ice message blog 控制器 前言 本篇博文主要介紹ASP.NET MVC中的三個核心元素:控制器、視圖與模型,以下思維導圖描述了本文的主要內容。 控制器 控制器簡介 在介紹控制器之前,簡單的介紹一下MVC工
跟廠長學PHP內核(二):源碼分析的環境與工具
compiler one upload info org print fin 圖形界面 waiting 本文主要介紹分析源碼的方式,其中包含環境的搭建、分析工具的安裝以及源碼調試的基本操作。 一、工具清單 PHP7.0.12 GDB CLion 二、源碼下載及安裝
DOM基礎練習代碼(二)
length 代碼 javascrip pretty 是否 pri nod 方法 return 上一篇給大家的三段代碼不知到大家有沒有練習呢?今天再給大家帶來兩段DOM的練習! 4.封裝函數,實現children功能,最好哎原型鏈上編程 1 Element.pro
《深入理解Spark-核心思想與源碼分析》(二)第二章Spark設計理念和基本架構
基礎知識 cut info 負責 驅動 源碼分析 spa spark 節點 若夫乘天地之正,而禦六氣之辯解,以遊無窮者,彼且惡乎待哉?
C# DataGridView控制元件與ListView控制元件的對比學習(二):ListView控制元件學習
一、定義: 表示Windows列表檢視控制元件,一般用來呈現資料,是一種輕量級的呈現資料的方法。 二、重要的屬性: 1、第一個非常重要的屬性是View:獲取或設定項在控制元件中的顯示方式,包括Details、LargeIcon、List、SmallI